From 2e9d4e967b39c7d63d3c4d6fd689c1e36b4fbe8c Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Sun, 22 Feb 2026 16:42:18 +0100 Subject: [PATCH 01/18] Initial commit for LongCat-Image. --- blueprints/Text to Image (LongCat-Image).json | 1 + comfy/model_base.py | 19 +++ comfy/model_detection.py | 30 ++++ comfy/sd.py | 5 + comfy/supported_models.py | 139 +++++++++++++++- comfy/text_encoders/longcat_image.py | 148 ++++++++++++++++++ comfy_extras/nodes_longcat_image.py | 40 +++++ nodes.py | 3 +- 8 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 blueprints/Text to Image (LongCat-Image).json create mode 100644 comfy/text_encoders/longcat_image.py create mode 100644 comfy_extras/nodes_longcat_image.py diff --git a/blueprints/Text to Image (LongCat-Image).json b/blueprints/Text to Image (LongCat-Image).json new file mode 100644 index 00000000000..36b021359ac --- /dev/null +++ b/blueprints/Text to Image (LongCat-Image).json @@ -0,0 +1 @@ +{"id": "a7e3b1c0-4f2d-4e8a-9b1c-longcat00001", "revision": 0, "last_node_id": 20, "last_link_id": 20, "nodes": [{"id": 1, "type": "lc-subgraph-001", "pos": [0, 1230], "size": [400, 470], "flags": {}, "order": 0, "mode": 0, "inputs": [{"label": "prompt", "name": "text", "type": "STRING", "widget": {"name": "text"}, "link": null}, {"name": "width", "type": "INT", "widget": {"name": "width"}, "link": null}, {"name": "height", "type": "INT", "widget": {"name": "height"}, "link": null}, {"name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": null}, {"name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": null}, {"name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": null}], "outputs": [{"localized_name": "IMAGE", "name": "IMAGE", "type": "IMAGE", "links": []}], "properties": {"proxyWidgets": [["-1", "text"], ["-1", "width"], ["-1", "height"], ["3", "seed"], ["3", "control_after_generate"], ["-1", "unet_name"], ["-1", "clip_name"], ["-1", "vae_name"]], "cnr_id": "comfy-core", "ver": "0.3.73", "enableTabs": false}, "widgets_values": ["A young Asian woman wearing a yellow knit sweater with a white necklace, sitting with her hands on her knees and a serene expression. The background is a rough brick wall with warm afternoon sunlight.", 768, 1344, null, null, "", "", "ae.safetensors"]}], "links": [], "groups": [], "definitions": {"subgraphs": [{"id": "lc-subgraph-001", "version": 1, "state": {"lastGroupId": 4, "lastNodeId": 20, "lastLinkId": 20, "lastRerouteId": 0}, "revision": 0, "config": {}, "name": "local-Text to Image (LongCat-Image)", "inputNode": {"id": -10, "bounding": [-80, 425, 120, 160]}, "outputNode": {"id": -20, "bounding": [1490, 415, 120, 60]}, "inputs": [{"id": "inp-text", "name": "text", "type": "STRING", "linkIds": [10], "label": "prompt", "pos": [20, 445]}, {"id": "inp-width", "name": "width", "type": "INT", "linkIds": [11], "pos": [20, 465]}, {"id": "inp-height", "name": "height", "type": "INT", "linkIds": [12], "pos": [20, 485]}, {"id": "inp-unet", "name": "unet_name", "type": "COMBO", "linkIds": [13], "pos": [20, 505]}, {"id": "inp-clip", "name": "clip_name", "type": "COMBO", "linkIds": [14], "pos": [20, 525]}, {"id": "inp-vae", "name": "vae_name", "type": "COMBO", "linkIds": [15], "pos": [20, 545]}], "outputs": [{"id": "out-image", "name": "IMAGE", "type": "IMAGE", "linkIds": [9], "localized_name": "IMAGE", "pos": [1510, 435]}], "widgets": [], "nodes": [{"id": 1, "type": "UNETLoader", "pos": [110, 200], "size": [270, 82], "flags": {}, "order": 0, "mode": 0, "inputs": [{"name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": 13}, {"name": "weight_dtype", "type": "COMBO", "widget": {"name": "weight_dtype"}, "link": null}], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [1]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "UNETLoader"}, "widgets_values": ["", "default"]}, {"id": 2, "type": "CLIPLoader", "pos": [110, 330], "size": [270, 106], "flags": {}, "order": 1, "mode": 0, "inputs": [{"name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": 14}, {"name": "type", "type": "COMBO", "widget": {"name": "type"}, "link": null}, {"name": "device", "shape": 7, "type": "COMBO", "widget": {"name": "device"}, "link": null}], "outputs": [{"name": "CLIP", "type": "CLIP", "links": [2]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPLoader"}, "widgets_values": ["", "longcat_image", "default"]}, {"id": 3, "type": "VAELoader", "pos": [110, 480], "size": [270, 58], "flags": {}, "order": 2, "mode": 0, "inputs": [{"name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": 15}], "outputs": [{"name": "VAE", "type": "VAE", "links": [3]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "VAELoader", "models": [{"name": "ae.safetensors", "url": "https://huggingface.co/black-forest-labs/FLUX.1-schnell/resolve/main/ae.safetensors", "directory": "vae"}]}, "widgets_values": ["ae.safetensors"]}, {"id": 4, "type": "CLIPTextEncodeLongCatImage", "pos": [430, 200], "size": [410, 320], "flags": {}, "order": 3, "mode": 0, "inputs": [{"name": "clip", "type": "CLIP", "link": 2}, {"name": "text", "type": "STRING", "widget": {"name": "text"}, "link": 10}, {"name": "guidance", "type": "FLOAT", "widget": {"name": "guidance"}, "link": null}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [4, 5]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPTextEncodeLongCatImage"}, "widgets_values": ["", 4.0]}, {"id": 5, "type": "ConditioningZeroOut", "pos": [640, 620], "size": [204, 26], "flags": {}, "order": 4, "mode": 0, "inputs": [{"name": "conditioning", "type": "CONDITIONING", "link": 5}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [6]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "ConditioningZeroOut"}, "widgets_values": []}, {"id": 6, "type": "EmptySD3LatentImage", "pos": [110, 630], "size": [260, 106], "flags": {}, "order": 5, "mode": 0, "inputs": [{"name": "width", "type": "INT", "widget": {"name": "width"}, "link": 11}, {"name": "height", "type": "INT", "widget": {"name": "height"}, "link": 12}, {"name": "batch_size", "type": "INT", "widget": {"name": "batch_size"}, "link": null}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [7]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "EmptySD3LatentImage"}, "widgets_values": [768, 1344, 1]}, {"id": 7, "type": "KSampler", "pos": [880, 270], "size": [315, 262], "flags": {}, "order": 6, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 1}, {"name": "positive", "type": "CONDITIONING", "link": 4}, {"name": "negative", "type": "CONDITIONING", "link": 6}, {"name": "latent_image", "type": "LATENT", "link": 7}, {"name": "seed", "type": "INT", "widget": {"name": "seed"}, "link": null}, {"name": "steps", "type": "INT", "widget": {"name": "steps"}, "link": null}, {"name": "cfg", "type": "FLOAT", "widget": {"name": "cfg"}, "link": null}, {"name": "sampler_name", "type": "COMBO", "widget": {"name": "sampler_name"}, "link": null}, {"name": "scheduler", "type": "COMBO", "widget": {"name": "scheduler"}, "link": null}, {"name": "denoise", "type": "FLOAT", "widget": {"name": "denoise"}, "link": null}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [8]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "KSampler"}, "widgets_values": [0, "randomize", 50, 4.0, "euler", "simple", 1]}, {"id": 8, "type": "VAEDecode", "pos": [1220, 160], "size": [210, 46], "flags": {}, "order": 7, "mode": 0, "inputs": [{"name": "samples", "type": "LATENT", "link": 8}, {"name": "vae", "type": "VAE", "link": 3}], "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [9]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "VAEDecode"}, "widgets_values": []}], "groups": [{"id": 1, "title": "Image size", "bounding": [100, 560, 290, 200], "color": "#3f789e", "font_size": 24, "flags": {}}, {"id": 2, "title": "Prompt", "bounding": [410, 130, 450, 540], "color": "#3f789e", "font_size": 24, "flags": {}}, {"id": 3, "title": "Models", "bounding": [100, 130, 290, 413], "color": "#3f789e", "font_size": 24, "flags": {}}], "links": [{"id": 1, "origin_id": 1, "origin_slot": 0, "target_id": 7, "target_slot": 0, "type": "MODEL"}, {"id": 2, "origin_id": 2, "origin_slot": 0, "target_id": 4, "target_slot": 0, "type": "CLIP"}, {"id": 3, "origin_id": 3, "origin_slot": 0, "target_id": 8, "target_slot": 1, "type": "VAE"}, {"id": 4, "origin_id": 4, "origin_slot": 0, "target_id": 7, "target_slot": 1, "type": "CONDITIONING"}, {"id": 5, "origin_id": 4, "origin_slot": 0, "target_id": 5, "target_slot": 0, "type": "CONDITIONING"}, {"id": 6, "origin_id": 5, "origin_slot": 0, "target_id": 7, "target_slot": 2, "type": "CONDITIONING"}, {"id": 7, "origin_id": 6, "origin_slot": 0, "target_id": 7, "target_slot": 3, "type": "LATENT"}, {"id": 8, "origin_id": 7, "origin_slot": 0, "target_id": 8, "target_slot": 0, "type": "LATENT"}, {"id": 9, "origin_id": 8, "origin_slot": 0, "target_id": -20, "target_slot": 0, "type": "IMAGE"}, {"id": 10, "origin_id": -10, "origin_slot": 0, "target_id": 4, "target_slot": 1, "type": "STRING"}, {"id": 11, "origin_id": -10, "origin_slot": 1, "target_id": 6, "target_slot": 0, "type": "INT"}, {"id": 12, "origin_id": -10, "origin_slot": 2, "target_id": 6, "target_slot": 1, "type": "INT"}, {"id": 13, "origin_id": -10, "origin_slot": 3, "target_id": 1, "target_slot": 0, "type": "COMBO"}, {"id": 14, "origin_id": -10, "origin_slot": 4, "target_id": 2, "target_slot": 0, "type": "COMBO"}, {"id": 15, "origin_id": -10, "origin_slot": 5, "target_id": 3, "target_slot": 0, "type": "COMBO"}], "extra": {"workflowRendererVersion": "LG"}, "category": "Image generation and editing/Text to image"}]}, "config": {}, "extra": {"frontendVersion": "1.37.10", "workflowRendererVersion": "LG"}, "version": 0.4} diff --git a/comfy/model_base.py b/comfy/model_base.py index 8f852e3c6cf..85cd30bae47 100644 --- a/comfy/model_base.py +++ b/comfy/model_base.py @@ -925,6 +925,25 @@ def extra_conds_shapes(self, **kwargs): out['ref_latents'] = list([1, 16, sum(map(lambda a: math.prod(a.size()[2:]), ref_latents))]) return out +class LongCatImage(Flux): + def _apply_model(self, x, t, c_concat=None, c_crossattn=None, control=None, transformer_options={}, **kwargs): + transformer_options = transformer_options.copy() + rope_opts = transformer_options.get("rope_options", {}) + rope_opts = dict(rope_opts) + rope_opts.setdefault("shift_t", 1.0) + rope_opts.setdefault("shift_y", 512.0) + rope_opts.setdefault("shift_x", 512.0) + transformer_options["rope_options"] = rope_opts + return super()._apply_model(x, t, c_concat, c_crossattn, control, transformer_options, **kwargs) + + def encode_adm(self, **kwargs): + return None + + def extra_conds(self, **kwargs): + out = super().extra_conds(**kwargs) + out.pop('guidance', None) + return out + class Flux2(Flux): def extra_conds(self, **kwargs): out = super().extra_conds(**kwargs) diff --git a/comfy/model_detection.py b/comfy/model_detection.py index 030ae6980e9..58fe514c454 100644 --- a/comfy/model_detection.py +++ b/comfy/model_detection.py @@ -282,6 +282,36 @@ def detect_unet_config(state_dict, key_prefix, metadata=None): return dit_config + if '{}x_embedder.weight'.format(key_prefix) in state_dict_keys and '{}transformer_blocks.0.attn.to_q.weight'.format(key_prefix) in state_dict_keys and '{}single_transformer_blocks.0.attn.to_q.weight'.format(key_prefix) in state_dict_keys: #LongCat-Image (diffusers format, Flux variant) + dit_config = {} + dit_config["image_model"] = "flux" + dit_config["axes_dim"] = [16, 56, 56] + dit_config["theta"] = 10000 + dit_config["qkv_bias"] = True + dit_config["txt_ids_dims"] = [1, 2] + + w = state_dict['{}x_embedder.weight'.format(key_prefix)] + dit_config["hidden_size"] = w.shape[0] + dit_config["in_channels"] = w.shape[1] // 4 + dit_config["out_channels"] = dit_config["in_channels"] + dit_config["patch_size"] = 2 + + ctx_key = '{}context_embedder.weight'.format(key_prefix) + if ctx_key in state_dict_keys: + dit_config["context_in_dim"] = state_dict[ctx_key].shape[1] + else: + dit_config["context_in_dim"] = 3584 + + dit_config["vec_in_dim"] = None + dit_config["guidance_embed"] = False + dit_config["mlp_ratio"] = 4.0 + dit_config["num_heads"] = dit_config["hidden_size"] // sum(dit_config["axes_dim"]) + + dit_config["depth"] = count_blocks(state_dict_keys, '{}transformer_blocks.'.format(key_prefix) + '{}.') + dit_config["depth_single_blocks"] = count_blocks(state_dict_keys, '{}single_transformer_blocks.'.format(key_prefix) + '{}.') + + return dit_config + if '{}t5_yproj.weight'.format(key_prefix) in state_dict_keys: #Genmo mochi preview dit_config = {} dit_config["image_model"] = "mochi_preview" diff --git a/comfy/sd.py b/comfy/sd.py index de119eb8e1a..7713d467840 100644 --- a/comfy/sd.py +++ b/comfy/sd.py @@ -60,6 +60,7 @@ import comfy.text_encoders.newbie import comfy.text_encoders.anima import comfy.text_encoders.ace15 +import comfy.text_encoders.longcat_image import comfy.model_patcher import comfy.lora @@ -1160,6 +1161,7 @@ class CLIPType(Enum): KANDINSKY5_IMAGE = 23 NEWBIE = 24 FLUX2 = 25 + LONGCAT_IMAGE = 26 def load_clip(ckpt_paths, embedding_directory=None, clip_type=CLIPType.STABLE_DIFFUSION, model_options={}): @@ -1372,6 +1374,9 @@ class EmptyClass: if clip_type == CLIPType.HUNYUAN_IMAGE: clip_target.clip = comfy.text_encoders.hunyuan_image.te(byt5=False, **llama_detect(clip_data)) clip_target.tokenizer = comfy.text_encoders.hunyuan_image.HunyuanImageTokenizer + elif clip_type == CLIPType.LONGCAT_IMAGE: + clip_target.clip = comfy.text_encoders.longcat_image.te(**llama_detect(clip_data)) + clip_target.tokenizer = comfy.text_encoders.longcat_image.LongCatImageTokenizer else: clip_target.clip = comfy.text_encoders.qwen_image.te(**llama_detect(clip_data)) clip_target.tokenizer = comfy.text_encoders.qwen_image.QwenImageTokenizer diff --git a/comfy/supported_models.py b/comfy/supported_models.py index 6d08ff0a55f..2431a3fb33d 100644 --- a/comfy/supported_models.py +++ b/comfy/supported_models.py @@ -25,6 +25,7 @@ import comfy.text_encoders.z_image import comfy.text_encoders.anima import comfy.text_encoders.ace15 +import comfy.text_encoders.longcat_image from . import supported_models_base from . import latent_formats @@ -1677,6 +1678,142 @@ def clip_target(self, state_dict={}): return supported_models_base.ClipTarget(comfy.text_encoders.ace15.ACE15Tokenizer, comfy.text_encoders.ace15.te(**detect)) -models = [LotusD, Stable_Zero123, SD15_instructpix2pix, SD15, SD20, SD21UnclipL, SD21UnclipH, SDXL_instructpix2pix, SDXLRefiner, SDXL, SSD1B, KOALA_700M, KOALA_1B, Segmind_Vega, SD_X4Upscaler, Stable_Cascade_C, Stable_Cascade_B, SV3D_u, SV3D_p, SD3, StableAudio, AuraFlow, PixArtAlpha, PixArtSigma, HunyuanDiT, HunyuanDiT1, FluxInpaint, Flux, FluxSchnell, GenmoMochi, LTXV, LTXAV, HunyuanVideo15_SR_Distilled, HunyuanVideo15, HunyuanImage21Refiner, HunyuanImage21, HunyuanVideoSkyreelsI2V, HunyuanVideoI2V, HunyuanVideo, CosmosT2V, CosmosI2V, CosmosT2IPredict2, CosmosI2VPredict2, ZImage, Lumina2, WAN22_T2V, WAN21_T2V, WAN21_I2V, WAN21_FunControl2V, WAN21_Vace, WAN21_Camera, WAN22_Camera, WAN22_S2V, WAN21_HuMo, WAN22_Animate, WAN21_FlowRVS, Hunyuan3Dv2mini, Hunyuan3Dv2, Hunyuan3Dv2_1, HiDream, Chroma, ChromaRadiance, ACEStep, ACEStep15, Omnigen2, QwenImage, Flux2, Kandinsky5Image, Kandinsky5, Anima] +class LongCatImage(supported_models_base.BASE): + unet_config = { + "image_model": "flux", + "guidance_embed": False, + "vec_in_dim": None, + "context_in_dim": 3584, + "txt_ids_dims": [1, 2], + } + + sampling_settings = { + } + + unet_extra_config = {} + latent_format = latent_formats.Flux + + memory_usage_factor = 2.5 + + supported_inference_dtypes = [torch.bfloat16, torch.float16, torch.float32] + + vae_key_prefix = ["vae."] + text_encoder_key_prefix = ["text_encoders."] + + def process_unet_state_dict(self, state_dict): + out_sd = {} + double_q, double_k, double_v = {}, {}, {} + double_tq, double_tk, double_tv = {}, {}, {} + single_q, single_k, single_v, single_mlp = {}, {}, {}, {} + + for k, v in state_dict.items(): + if k.startswith("transformer_blocks."): + idx = k.split(".")[1] + rest = ".".join(k.split(".")[2:]) + prefix = "double_blocks.{}.".format(idx) + + if rest.startswith("norm1.linear."): + out_sd[prefix + "img_mod.lin." + rest.split(".")[-1]] = v + elif rest.startswith("norm1_context.linear."): + out_sd[prefix + "txt_mod.lin." + rest.split(".")[-1]] = v + elif rest.startswith("attn.to_q."): + double_q[idx + "." + rest.split(".")[-1]] = v + elif rest.startswith("attn.to_k."): + double_k[idx + "." + rest.split(".")[-1]] = v + elif rest.startswith("attn.to_v."): + double_v[idx + "." + rest.split(".")[-1]] = v + elif rest == "attn.norm_q.weight": + out_sd[prefix + "img_attn.norm.query_norm.weight"] = v + elif rest == "attn.norm_k.weight": + out_sd[prefix + "img_attn.norm.key_norm.weight"] = v + elif rest.startswith("attn.to_out.0."): + out_sd[prefix + "img_attn.proj." + rest.split(".")[-1]] = v + elif rest.startswith("attn.add_q_proj."): + double_tq[idx + "." + rest.split(".")[-1]] = v + elif rest.startswith("attn.add_k_proj."): + double_tk[idx + "." + rest.split(".")[-1]] = v + elif rest.startswith("attn.add_v_proj."): + double_tv[idx + "." + rest.split(".")[-1]] = v + elif rest == "attn.norm_added_q.weight": + out_sd[prefix + "txt_attn.norm.query_norm.weight"] = v + elif rest == "attn.norm_added_k.weight": + out_sd[prefix + "txt_attn.norm.key_norm.weight"] = v + elif rest.startswith("attn.to_add_out."): + out_sd[prefix + "txt_attn.proj." + rest.split(".")[-1]] = v + elif rest.startswith("ff.net.0.proj."): + out_sd[prefix + "img_mlp.0." + rest.split(".")[-1]] = v + elif rest.startswith("ff.net.2."): + out_sd[prefix + "img_mlp.2." + rest.split(".")[-1]] = v + elif rest.startswith("ff_context.net.0.proj."): + out_sd[prefix + "txt_mlp.0." + rest.split(".")[-1]] = v + elif rest.startswith("ff_context.net.2."): + out_sd[prefix + "txt_mlp.2." + rest.split(".")[-1]] = v + else: + out_sd["double_blocks.{}.{}".format(idx, rest)] = v + + elif k.startswith("single_transformer_blocks."): + idx = k.split(".")[1] + rest = ".".join(k.split(".")[2:]) + prefix = "single_blocks.{}.".format(idx) + + if rest.startswith("norm.linear."): + out_sd[prefix + "modulation.lin." + rest.split(".")[-1]] = v + elif rest.startswith("attn.to_q."): + single_q[idx + "." + rest.split(".")[-1]] = v + elif rest.startswith("attn.to_k."): + single_k[idx + "." + rest.split(".")[-1]] = v + elif rest.startswith("attn.to_v."): + single_v[idx + "." + rest.split(".")[-1]] = v + elif rest == "attn.norm_q.weight": + out_sd[prefix + "norm.query_norm.weight"] = v + elif rest == "attn.norm_k.weight": + out_sd[prefix + "norm.key_norm.weight"] = v + elif rest.startswith("proj_mlp."): + single_mlp[idx + "." + rest.split(".")[-1]] = v + elif rest.startswith("proj_out."): + out_sd[prefix + "linear2." + rest.split(".")[-1]] = v + else: + out_sd["single_blocks.{}.{}".format(idx, rest)] = v + + elif k == "x_embedder.weight" or k == "x_embedder.bias": + out_sd["img_in." + k.split(".")[-1]] = v + elif k == "context_embedder.weight" or k == "context_embedder.bias": + out_sd["txt_in." + k.split(".")[-1]] = v + elif k.startswith("time_embed.timestep_embedder.linear_1."): + out_sd["time_in.in_layer." + k.split(".")[-1]] = v + elif k.startswith("time_embed.timestep_embedder.linear_2."): + out_sd["time_in.out_layer." + k.split(".")[-1]] = v + elif k.startswith("norm_out.linear."): + out_sd["final_layer.adaLN_modulation.1." + k.split(".")[-1]] = v + elif k == "proj_out.weight" or k == "proj_out.bias": + out_sd["final_layer.linear." + k.split(".")[-1]] = v + else: + out_sd[k] = v + + for suffix in ["weight", "bias"]: + for idx in sorted(set(x.split(".")[0] for x in double_q)): + qk = idx + "." + suffix + if qk in double_q and qk in double_k and qk in double_v: + out_sd["double_blocks.{}.img_attn.qkv.{}".format(idx, suffix)] = torch.cat([double_q[qk], double_k[qk], double_v[qk]], dim=0) + if qk in double_tq and qk in double_tk and qk in double_tv: + out_sd["double_blocks.{}.txt_attn.qkv.{}".format(idx, suffix)] = torch.cat([double_tq[qk], double_tk[qk], double_tv[qk]], dim=0) + + for idx in sorted(set(x.split(".")[0] for x in single_q)): + qk = idx + "." + suffix + if qk in single_q and qk in single_k and qk in single_v and qk in single_mlp: + out_sd["single_blocks.{}.linear1.{}".format(idx, suffix)] = torch.cat([single_q[qk], single_k[qk], single_v[qk], single_mlp[qk]], dim=0) + + return out_sd + + def get_model(self, state_dict, prefix="", device=None): + out = model_base.LongCatImage(self, device=device) + return out + + def clip_target(self, state_dict={}): + pref = self.text_encoder_key_prefix[0] + hunyuan_detect = comfy.text_encoders.hunyuan_video.llama_detect(state_dict, "{}qwen25_7b.transformer.".format(pref)) + return supported_models_base.ClipTarget(comfy.text_encoders.longcat_image.LongCatImageTokenizer, comfy.text_encoders.longcat_image.te(**hunyuan_detect)) + +models = [LotusD, Stable_Zero123, SD15_instructpix2pix, SD15, SD20, SD21UnclipL, SD21UnclipH, SDXL_instructpix2pix, SDXLRefiner, SDXL, SSD1B, KOALA_700M, KOALA_1B, Segmind_Vega, SD_X4Upscaler, Stable_Cascade_C, Stable_Cascade_B, SV3D_u, SV3D_p, SD3, StableAudio, AuraFlow, PixArtAlpha, PixArtSigma, HunyuanDiT, HunyuanDiT1, FluxInpaint, Flux, FluxSchnell, GenmoMochi, LTXV, LTXAV, HunyuanVideo15_SR_Distilled, HunyuanVideo15, HunyuanImage21Refiner, HunyuanImage21, HunyuanVideoSkyreelsI2V, HunyuanVideoI2V, HunyuanVideo, CosmosT2V, CosmosI2V, CosmosT2IPredict2, CosmosI2VPredict2, ZImage, Lumina2, WAN22_T2V, WAN21_T2V, WAN21_I2V, WAN21_FunControl2V, WAN21_Vace, WAN21_Camera, WAN22_Camera, WAN22_S2V, WAN21_HuMo, WAN22_Animate, WAN21_FlowRVS, Hunyuan3Dv2mini, Hunyuan3Dv2, Hunyuan3Dv2_1, HiDream, Chroma, ChromaRadiance, ACEStep, ACEStep15, Omnigen2, QwenImage, LongCatImage, Flux2, Kandinsky5Image, Kandinsky5, Anima] models += [SVD_img2vid] diff --git a/comfy/text_encoders/longcat_image.py b/comfy/text_encoders/longcat_image.py new file mode 100644 index 00000000000..84e4860be56 --- /dev/null +++ b/comfy/text_encoders/longcat_image.py @@ -0,0 +1,148 @@ +import re +import numbers +import torch +from comfy import sd1_clip +from comfy.text_encoders.qwen_image import Qwen25_7BVLITokenizer, Qwen25_7BVLIModel + + +QUOTE_PAIRS = [("'", "'"), ('"', '"'), ("\u2018", "\u2019"), ("\u201c", "\u201d")] +QUOTE_PATTERN = "|".join( + [re.escape(q1) + r"[^" + re.escape(q1 + q2) + r"]*?" + re.escape(q2) for q1, q2 in QUOTE_PAIRS] +) +WORD_INTERNAL_QUOTE_RE = re.compile(r"[a-zA-Z]+'[a-zA-Z]+") + + +def split_quotation(prompt): + matches = WORD_INTERNAL_QUOTE_RE.findall(prompt) + mapping = [] + for i, word_src in enumerate(set(matches)): + word_tgt = "longcat_$##$_longcat" * (i + 1) + prompt = prompt.replace(word_src, word_tgt) + mapping.append((word_src, word_tgt)) + + parts = re.split(f"({QUOTE_PATTERN})", prompt) + result = [] + for part in parts: + for word_src, word_tgt in mapping: + part = part.replace(word_tgt, word_src) + if not part: + continue + is_quoted = bool(re.match(QUOTE_PATTERN, part)) + result.append((part, is_quoted)) + return result + + +class LongCatImageBaseTokenizer(Qwen25_7BVLITokenizer): + def tokenize_with_weights(self, text, return_word_ids=False, **kwargs): + parts = split_quotation(text) + all_tokens = [] + for part_text, is_quoted in parts: + if is_quoted: + for char in part_text: + ids = self.tokenizer(char, add_special_tokens=False)["input_ids"] + all_tokens.extend(ids) + else: + ids = self.tokenizer(part_text, add_special_tokens=False)["input_ids"] + all_tokens.extend(ids) + + max_len = self.max_length if self.max_length < 99999999 else 512 + if len(all_tokens) > max_len: + all_tokens = all_tokens[:max_len] + + output = [(t, 1.0) for t in all_tokens] + return [output] + + +class LongCatImageTokenizer(sd1_clip.SD1Tokenizer): + def __init__(self, embedding_directory=None, tokenizer_data={}): + super().__init__(embedding_directory=embedding_directory, tokenizer_data=tokenizer_data, name="qwen25_7b", tokenizer=LongCatImageBaseTokenizer) + self.longcat_template_prefix = "<|im_start|>system\nAs an image captioning expert, generate a descriptive text prompt based on an image content, suitable for input to a text-to-image model.<|im_end|>\n<|im_start|>user\n" + self.longcat_template_suffix = "<|im_end|>\n<|im_start|>assistant\n" + + def tokenize_with_weights(self, text, return_word_ids=False, **kwargs): + skip_template = False + if text.startswith('<|im_start|>'): + skip_template = True + if text.startswith('<|start_header_id|>'): + skip_template = True + if text == '': + text = ' ' + + base_tok = getattr(self, "qwen25_7b") + if skip_template: + tokens = super().tokenize_with_weights(text, return_word_ids=return_word_ids, disable_weights=True, **kwargs) + else: + prefix_ids = base_tok.tokenizer(self.longcat_template_prefix, add_special_tokens=False)["input_ids"] + suffix_ids = base_tok.tokenizer(self.longcat_template_suffix, add_special_tokens=False)["input_ids"] + + prompt_tokens = base_tok.tokenize_with_weights(text, return_word_ids=return_word_ids, **kwargs) + prompt_pairs = prompt_tokens[0] + + prefix_pairs = [(t, 1.0) for t in prefix_ids] + suffix_pairs = [(t, 1.0) for t in suffix_ids] + + combined = prefix_pairs + prompt_pairs + suffix_pairs + tokens = {"qwen25_7b": [combined]} + + return tokens + + +class LongCatImageTEModel(sd1_clip.SD1ClipModel): + def __init__(self, device="cpu", dtype=None, model_options={}): + super().__init__(device=device, dtype=dtype, name="qwen25_7b", clip_model=Qwen25_7BVLIModel, model_options=model_options) + + def encode_token_weights(self, token_weight_pairs, template_end=-1): + out, pooled, extra = super().encode_token_weights(token_weight_pairs) + tok_pairs = token_weight_pairs["qwen25_7b"][0] + count_im_start = 0 + if template_end == -1: + for i, v in enumerate(tok_pairs): + elem = v[0] + if not torch.is_tensor(elem): + if isinstance(elem, numbers.Integral): + if elem == 151644 and count_im_start < 2: + template_end = i + count_im_start += 1 + + if out.shape[1] > (template_end + 3): + if tok_pairs[template_end + 1][0] == 872: + if tok_pairs[template_end + 2][0] == 198: + template_end += 3 + + suffix_start = None + for i in range(len(tok_pairs) - 1, -1, -1): + elem = tok_pairs[i][0] + if not torch.is_tensor(elem) and isinstance(elem, numbers.Integral): + if elem == 151644: + suffix_start = i + break + + out = out[:, template_end:] + + if "attention_mask" in extra: + extra["attention_mask"] = extra["attention_mask"][:, template_end:] + if extra["attention_mask"].sum() == torch.numel(extra["attention_mask"]): + extra.pop("attention_mask") + + if suffix_start is not None: + suffix_len = len(tok_pairs) - suffix_start + if suffix_len > 0 and out.shape[1] > suffix_len: + out = out[:, :-suffix_len] + if "attention_mask" in extra: + extra["attention_mask"] = extra["attention_mask"][:, :-suffix_len] + if extra["attention_mask"].sum() == torch.numel(extra["attention_mask"]): + extra.pop("attention_mask") + + return out, pooled, extra + + +def te(dtype_llama=None, llama_quantization_metadata=None): + class LongCatImageTEModel_(LongCatImageTEModel): + def __init__(self, device="cpu", dtype=None, model_options={}): + if llama_quantization_metadata is not None: + model_options = model_options.copy() + model_options["quantization_metadata"] = llama_quantization_metadata + if dtype_llama is not None: + dtype = dtype_llama + super().__init__(device=device, dtype=dtype, model_options=model_options) + return LongCatImageTEModel_ diff --git a/comfy_extras/nodes_longcat_image.py b/comfy_extras/nodes_longcat_image.py new file mode 100644 index 00000000000..5541c5ee315 --- /dev/null +++ b/comfy_extras/nodes_longcat_image.py @@ -0,0 +1,40 @@ +from typing_extensions import override +from comfy_api.latest import ComfyExtension, io + + +class CLIPTextEncodeLongCatImage(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="CLIPTextEncodeLongCatImage", + display_name="CLIP Text Encode (LongCat-Image)", + category="advanced/conditioning/longcat", + description="Text encoding for LongCat-Image with character-level quoted text support. Wrap text in quotes for accurate text rendering.", + inputs=[ + io.Clip.Input("clip"), + io.String.Input("text", multiline=True, dynamic_prompts=True), + io.Float.Input("guidance", default=4.0, min=0.0, max=100.0, step=0.1), + ], + outputs=[ + io.Conditioning.Output(), + ], + ) + + @classmethod + def execute(cls, clip, text, guidance) -> io.NodeOutput: + tokens = clip.tokenize(text) + return io.NodeOutput(clip.encode_from_tokens_scheduled(tokens, add_dict={"guidance": guidance})) + + encode = execute + + +class LongCatImageExtension(ComfyExtension): + @override + async def get_node_list(self) -> list[type[io.ComfyNode]]: + return [ + CLIPTextEncodeLongCatImage, + ] + + +async def comfy_entrypoint() -> LongCatImageExtension: + return LongCatImageExtension() diff --git a/nodes.py b/nodes.py index bff073e3030..adb2faa79ae 100644 --- a/nodes.py +++ b/nodes.py @@ -976,7 +976,7 @@ class CLIPLoader: @classmethod def INPUT_TYPES(s): return {"required": { "clip_name": (folder_paths.get_filename_list("text_encoders"), ), - "type": (["stable_diffusion", "stable_cascade", "sd3", "stable_audio", "mochi", "ltxv", "pixart", "cosmos", "lumina2", "wan", "hidream", "chroma", "ace", "omnigen2", "qwen_image", "hunyuan_image", "flux2", "ovis"], ), + "type": (["stable_diffusion", "stable_cascade", "sd3", "stable_audio", "mochi", "ltxv", "pixart", "cosmos", "lumina2", "wan", "hidream", "chroma", "ace", "omnigen2", "qwen_image", "hunyuan_image", "flux2", "ovis", "longcat_image"], ), }, "optional": { "device": (["default", "cpu"], {"advanced": True}), @@ -2429,6 +2429,7 @@ async def init_builtin_extra_nodes(): "nodes_tcfg.py", "nodes_context_windows.py", "nodes_qwen.py", + "nodes_longcat_image.py", "nodes_chroma_radiance.py", "nodes_model_patch.py", "nodes_easycache.py", From ab708b4b40d3df9959a44128446f799539a29c48 Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Sun, 22 Feb 2026 23:17:18 +0100 Subject: [PATCH 02/18] Should be working now. --- comfy/supported_models.py | 4 ++++ comfy/text_encoders/longcat_image.py | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/comfy/supported_models.py b/comfy/supported_models.py index 2431a3fb33d..aaf2e8a88ff 100644 --- a/comfy/supported_models.py +++ b/comfy/supported_models.py @@ -1784,6 +1784,10 @@ def process_unet_state_dict(self, state_dict): elif k.startswith("time_embed.timestep_embedder.linear_2."): out_sd["time_in.out_layer." + k.split(".")[-1]] = v elif k.startswith("norm_out.linear."): + # HF AdaLayerNormContinuous stores [scale | shift] but ComfyUI + # LastLayer expects [shift | scale], so swap the two halves. + half = v.shape[0] // 2 + v = torch.cat([v[half:], v[:half]], dim=0) out_sd["final_layer.adaLN_modulation.1." + k.split(".")[-1]] = v elif k == "proj_out.weight" or k == "proj_out.bias": out_sd["final_layer.linear." + k.split(".")[-1]] = v diff --git a/comfy/text_encoders/longcat_image.py b/comfy/text_encoders/longcat_image.py index 84e4860be56..3f91f7ce025 100644 --- a/comfy/text_encoders/longcat_image.py +++ b/comfy/text_encoders/longcat_image.py @@ -3,7 +3,9 @@ import torch from comfy import sd1_clip from comfy.text_encoders.qwen_image import Qwen25_7BVLITokenizer, Qwen25_7BVLIModel +import logging +logger = logging.getLogger(__name__) QUOTE_PAIRS = [("'", "'"), ('"', '"'), ("\u2018", "\u2019"), ("\u201c", "\u201d")] QUOTE_PATTERN = "|".join( @@ -33,6 +35,10 @@ def split_quotation(prompt): class LongCatImageBaseTokenizer(Qwen25_7BVLITokenizer): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.max_length = 512 + def tokenize_with_weights(self, text, return_word_ids=False, **kwargs): parts = split_quotation(text) all_tokens = [] @@ -45,11 +51,13 @@ def tokenize_with_weights(self, text, return_word_ids=False, **kwargs): ids = self.tokenizer(part_text, add_special_tokens=False)["input_ids"] all_tokens.extend(ids) - max_len = self.max_length if self.max_length < 99999999 else 512 - if len(all_tokens) > max_len: - all_tokens = all_tokens[:max_len] + if len(all_tokens) > self.max_length: + all_tokens = all_tokens[:self.max_length] + logger.warning(f"Truncated prompt to {self.max_length} tokens") output = [(t, 1.0) for t in all_tokens] + # Pad to max length + self.pad_tokens(output, self.max_length - len(output)) return [output] @@ -113,7 +121,7 @@ def encode_token_weights(self, token_weight_pairs, template_end=-1): for i in range(len(tok_pairs) - 1, -1, -1): elem = tok_pairs[i][0] if not torch.is_tensor(elem) and isinstance(elem, numbers.Integral): - if elem == 151644: + if elem == 151645: suffix_start = i break From 7959f6d3df1cac2db3a37ecfe10103497b561a23 Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Mon, 23 Feb 2026 20:36:14 +0100 Subject: [PATCH 03/18] Add CFGRenormLongCatImage Node. --- comfy_extras/nodes_longcat_image.py | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/comfy_extras/nodes_longcat_image.py b/comfy_extras/nodes_longcat_image.py index 5541c5ee315..6e8178cdb3d 100644 --- a/comfy_extras/nodes_longcat_image.py +++ b/comfy_extras/nodes_longcat_image.py @@ -1,3 +1,4 @@ +import torch from typing_extensions import override from comfy_api.latest import ComfyExtension, io @@ -28,11 +29,71 @@ def execute(cls, clip, text, guidance) -> io.NodeOutput: encode = execute +class CFGRenormLongCatImage(io.ComfyNode): + """Per-patch CFG renormalization matching HuggingFace's LongCat-Image pipeline. + + After standard CFG combination, rescales the noise prediction at each 2x2 patch + so its norm doesn't exceed the conditional prediction's norm. + """ + + @classmethod + def define_schema(cls): + return io.Schema( + node_id="CFGRenormLongCatImage", + display_name="CFG Renorm (LongCat-Image)", + category="advanced/model/longcat", + description="Applies per-patch CFG renormalization used by the LongCat-Image pipeline. Connect between the model loader and the sampler.", + inputs=[ + io.Model.Input("model"), + ], + outputs=[ + io.Model.Output(), + ], + ) + + @classmethod + def execute(cls, model) -> io.NodeOutput: + def cfg_renorm_post(args): + denoised = args["denoised"] + cond_denoised = args["cond_denoised"] + x = args["input"] + + B, C, H, W = denoised.shape + ps = 2 + + noise = x - denoised + noise_cond = x - cond_denoised + + noise_packed = noise.reshape(B, C, H // ps, ps, W // ps, ps) \ + .permute(0, 2, 4, 1, 3, 5) \ + .reshape(B, -1, C * ps * ps) + cond_packed = noise_cond.reshape(B, C, H // ps, ps, W // ps, ps) \ + .permute(0, 2, 4, 1, 3, 5) \ + .reshape(B, -1, C * ps * ps) + + noise_norm = torch.norm(noise_packed, dim=-1, keepdim=True) + cond_norm = torch.norm(cond_packed, dim=-1, keepdim=True) + + scale = (cond_norm / (noise_norm + 1e-8)).clamp(min=0.0, max=1.0) + + renormed = (noise_packed * scale) \ + .reshape(B, H // ps, W // ps, C, ps, ps) \ + .permute(0, 3, 1, 4, 2, 5) \ + .reshape(B, C, H, W) + + return x - renormed + + m = model.clone() + m.set_model_sampler_post_cfg_function(cfg_renorm_post, disable_cfg1_optimization=True) + return io.NodeOutput(m) + + class LongCatImageExtension(ComfyExtension): @override async def get_node_list(self) -> list[type[io.ComfyNode]]: return [ CLIPTextEncodeLongCatImage, + CFGRenormLongCatImage, ] From 61f14363855b37b0a05b761c3e9f80e7606d5501 Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Mon, 23 Feb 2026 20:37:51 +0100 Subject: [PATCH 04/18] Fix correct unet detection for LongCat-Image with ading required keys. --- comfy/supported_models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/comfy/supported_models.py b/comfy/supported_models.py index aaf2e8a88ff..12b2d3f0fd4 100644 --- a/comfy/supported_models.py +++ b/comfy/supported_models.py @@ -1687,6 +1687,12 @@ class LongCatImage(supported_models_base.BASE): "txt_ids_dims": [1, 2], } + required_keys = { + "x_embedder.weight": None, + "context_embedder.weight": None, + "single_transformer_blocks.10.attn.to_q.weight": None, + } + sampling_settings = { } From 1dcc4d568e8473cbabc513fb91d44b8b9cbc9a46 Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Mon, 23 Feb 2026 21:03:49 +0100 Subject: [PATCH 05/18] Update LongCat-Image blueprint. --- blueprints/Text to Image (LongCat-Image).json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blueprints/Text to Image (LongCat-Image).json b/blueprints/Text to Image (LongCat-Image).json index 36b021359ac..1a01ec30aa0 100644 --- a/blueprints/Text to Image (LongCat-Image).json +++ b/blueprints/Text to Image (LongCat-Image).json @@ -1 +1 @@ -{"id": "a7e3b1c0-4f2d-4e8a-9b1c-longcat00001", "revision": 0, "last_node_id": 20, "last_link_id": 20, "nodes": [{"id": 1, "type": "lc-subgraph-001", "pos": [0, 1230], "size": [400, 470], "flags": {}, "order": 0, "mode": 0, "inputs": [{"label": "prompt", "name": "text", "type": "STRING", "widget": {"name": "text"}, "link": null}, {"name": "width", "type": "INT", "widget": {"name": "width"}, "link": null}, {"name": "height", "type": "INT", "widget": {"name": "height"}, "link": null}, {"name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": null}, {"name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": null}, {"name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": null}], "outputs": [{"localized_name": "IMAGE", "name": "IMAGE", "type": "IMAGE", "links": []}], "properties": {"proxyWidgets": [["-1", "text"], ["-1", "width"], ["-1", "height"], ["3", "seed"], ["3", "control_after_generate"], ["-1", "unet_name"], ["-1", "clip_name"], ["-1", "vae_name"]], "cnr_id": "comfy-core", "ver": "0.3.73", "enableTabs": false}, "widgets_values": ["A young Asian woman wearing a yellow knit sweater with a white necklace, sitting with her hands on her knees and a serene expression. The background is a rough brick wall with warm afternoon sunlight.", 768, 1344, null, null, "", "", "ae.safetensors"]}], "links": [], "groups": [], "definitions": {"subgraphs": [{"id": "lc-subgraph-001", "version": 1, "state": {"lastGroupId": 4, "lastNodeId": 20, "lastLinkId": 20, "lastRerouteId": 0}, "revision": 0, "config": {}, "name": "local-Text to Image (LongCat-Image)", "inputNode": {"id": -10, "bounding": [-80, 425, 120, 160]}, "outputNode": {"id": -20, "bounding": [1490, 415, 120, 60]}, "inputs": [{"id": "inp-text", "name": "text", "type": "STRING", "linkIds": [10], "label": "prompt", "pos": [20, 445]}, {"id": "inp-width", "name": "width", "type": "INT", "linkIds": [11], "pos": [20, 465]}, {"id": "inp-height", "name": "height", "type": "INT", "linkIds": [12], "pos": [20, 485]}, {"id": "inp-unet", "name": "unet_name", "type": "COMBO", "linkIds": [13], "pos": [20, 505]}, {"id": "inp-clip", "name": "clip_name", "type": "COMBO", "linkIds": [14], "pos": [20, 525]}, {"id": "inp-vae", "name": "vae_name", "type": "COMBO", "linkIds": [15], "pos": [20, 545]}], "outputs": [{"id": "out-image", "name": "IMAGE", "type": "IMAGE", "linkIds": [9], "localized_name": "IMAGE", "pos": [1510, 435]}], "widgets": [], "nodes": [{"id": 1, "type": "UNETLoader", "pos": [110, 200], "size": [270, 82], "flags": {}, "order": 0, "mode": 0, "inputs": [{"name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": 13}, {"name": "weight_dtype", "type": "COMBO", "widget": {"name": "weight_dtype"}, "link": null}], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [1]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "UNETLoader"}, "widgets_values": ["", "default"]}, {"id": 2, "type": "CLIPLoader", "pos": [110, 330], "size": [270, 106], "flags": {}, "order": 1, "mode": 0, "inputs": [{"name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": 14}, {"name": "type", "type": "COMBO", "widget": {"name": "type"}, "link": null}, {"name": "device", "shape": 7, "type": "COMBO", "widget": {"name": "device"}, "link": null}], "outputs": [{"name": "CLIP", "type": "CLIP", "links": [2]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPLoader"}, "widgets_values": ["", "longcat_image", "default"]}, {"id": 3, "type": "VAELoader", "pos": [110, 480], "size": [270, 58], "flags": {}, "order": 2, "mode": 0, "inputs": [{"name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": 15}], "outputs": [{"name": "VAE", "type": "VAE", "links": [3]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "VAELoader", "models": [{"name": "ae.safetensors", "url": "https://huggingface.co/black-forest-labs/FLUX.1-schnell/resolve/main/ae.safetensors", "directory": "vae"}]}, "widgets_values": ["ae.safetensors"]}, {"id": 4, "type": "CLIPTextEncodeLongCatImage", "pos": [430, 200], "size": [410, 320], "flags": {}, "order": 3, "mode": 0, "inputs": [{"name": "clip", "type": "CLIP", "link": 2}, {"name": "text", "type": "STRING", "widget": {"name": "text"}, "link": 10}, {"name": "guidance", "type": "FLOAT", "widget": {"name": "guidance"}, "link": null}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [4, 5]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPTextEncodeLongCatImage"}, "widgets_values": ["", 4.0]}, {"id": 5, "type": "ConditioningZeroOut", "pos": [640, 620], "size": [204, 26], "flags": {}, "order": 4, "mode": 0, "inputs": [{"name": "conditioning", "type": "CONDITIONING", "link": 5}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [6]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "ConditioningZeroOut"}, "widgets_values": []}, {"id": 6, "type": "EmptySD3LatentImage", "pos": [110, 630], "size": [260, 106], "flags": {}, "order": 5, "mode": 0, "inputs": [{"name": "width", "type": "INT", "widget": {"name": "width"}, "link": 11}, {"name": "height", "type": "INT", "widget": {"name": "height"}, "link": 12}, {"name": "batch_size", "type": "INT", "widget": {"name": "batch_size"}, "link": null}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [7]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "EmptySD3LatentImage"}, "widgets_values": [768, 1344, 1]}, {"id": 7, "type": "KSampler", "pos": [880, 270], "size": [315, 262], "flags": {}, "order": 6, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 1}, {"name": "positive", "type": "CONDITIONING", "link": 4}, {"name": "negative", "type": "CONDITIONING", "link": 6}, {"name": "latent_image", "type": "LATENT", "link": 7}, {"name": "seed", "type": "INT", "widget": {"name": "seed"}, "link": null}, {"name": "steps", "type": "INT", "widget": {"name": "steps"}, "link": null}, {"name": "cfg", "type": "FLOAT", "widget": {"name": "cfg"}, "link": null}, {"name": "sampler_name", "type": "COMBO", "widget": {"name": "sampler_name"}, "link": null}, {"name": "scheduler", "type": "COMBO", "widget": {"name": "scheduler"}, "link": null}, {"name": "denoise", "type": "FLOAT", "widget": {"name": "denoise"}, "link": null}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [8]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "KSampler"}, "widgets_values": [0, "randomize", 50, 4.0, "euler", "simple", 1]}, {"id": 8, "type": "VAEDecode", "pos": [1220, 160], "size": [210, 46], "flags": {}, "order": 7, "mode": 0, "inputs": [{"name": "samples", "type": "LATENT", "link": 8}, {"name": "vae", "type": "VAE", "link": 3}], "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [9]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "VAEDecode"}, "widgets_values": []}], "groups": [{"id": 1, "title": "Image size", "bounding": [100, 560, 290, 200], "color": "#3f789e", "font_size": 24, "flags": {}}, {"id": 2, "title": "Prompt", "bounding": [410, 130, 450, 540], "color": "#3f789e", "font_size": 24, "flags": {}}, {"id": 3, "title": "Models", "bounding": [100, 130, 290, 413], "color": "#3f789e", "font_size": 24, "flags": {}}], "links": [{"id": 1, "origin_id": 1, "origin_slot": 0, "target_id": 7, "target_slot": 0, "type": "MODEL"}, {"id": 2, "origin_id": 2, "origin_slot": 0, "target_id": 4, "target_slot": 0, "type": "CLIP"}, {"id": 3, "origin_id": 3, "origin_slot": 0, "target_id": 8, "target_slot": 1, "type": "VAE"}, {"id": 4, "origin_id": 4, "origin_slot": 0, "target_id": 7, "target_slot": 1, "type": "CONDITIONING"}, {"id": 5, "origin_id": 4, "origin_slot": 0, "target_id": 5, "target_slot": 0, "type": "CONDITIONING"}, {"id": 6, "origin_id": 5, "origin_slot": 0, "target_id": 7, "target_slot": 2, "type": "CONDITIONING"}, {"id": 7, "origin_id": 6, "origin_slot": 0, "target_id": 7, "target_slot": 3, "type": "LATENT"}, {"id": 8, "origin_id": 7, "origin_slot": 0, "target_id": 8, "target_slot": 0, "type": "LATENT"}, {"id": 9, "origin_id": 8, "origin_slot": 0, "target_id": -20, "target_slot": 0, "type": "IMAGE"}, {"id": 10, "origin_id": -10, "origin_slot": 0, "target_id": 4, "target_slot": 1, "type": "STRING"}, {"id": 11, "origin_id": -10, "origin_slot": 1, "target_id": 6, "target_slot": 0, "type": "INT"}, {"id": 12, "origin_id": -10, "origin_slot": 2, "target_id": 6, "target_slot": 1, "type": "INT"}, {"id": 13, "origin_id": -10, "origin_slot": 3, "target_id": 1, "target_slot": 0, "type": "COMBO"}, {"id": 14, "origin_id": -10, "origin_slot": 4, "target_id": 2, "target_slot": 0, "type": "COMBO"}, {"id": 15, "origin_id": -10, "origin_slot": 5, "target_id": 3, "target_slot": 0, "type": "COMBO"}], "extra": {"workflowRendererVersion": "LG"}, "category": "Image generation and editing/Text to image"}]}, "config": {}, "extra": {"frontendVersion": "1.37.10", "workflowRendererVersion": "LG"}, "version": 0.4} +{"id": "a7e3b1c0-4f2d-4e8a-9b1c-longcat00001", "revision": 0, "last_node_id": 20, "last_link_id": 20, "nodes": [{"id": 1, "type": "lc-subgraph-001", "pos": [0, 1230], "size": [400, 470], "flags": {}, "order": 0, "mode": 0, "inputs": [{"label": "prompt", "name": "text", "type": "STRING", "widget": {"name": "text"}, "link": null}, {"name": "width", "type": "INT", "widget": {"name": "width"}, "link": null}, {"name": "height", "type": "INT", "widget": {"name": "height"}, "link": null}, {"name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": null}, {"name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": null}, {"name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": null}], "outputs": [{"localized_name": "IMAGE", "name": "IMAGE", "type": "IMAGE", "links": []}], "properties": {"proxyWidgets": [["-1", "text"], ["-1", "width"], ["-1", "height"], ["7", "seed"], ["7", "control_after_generate"], ["-1", "unet_name"], ["-1", "clip_name"], ["-1", "vae_name"]], "cnr_id": "comfy-core", "ver": "0.3.73", "enableTabs": false}, "widgets_values": ["A young Asian woman wearing a yellow knit sweater with a white necklace, sitting with her hands on her knees and a serene expression. The background is a rough brick wall with warm afternoon sunlight.", 768, 1344, null, null, "longcat_image.safetensors", "qwen_2.5_vl_7b.safetensors", "ae.safetensors"]}], "links": [], "groups": [], "definitions": {"subgraphs": [{"id": "lc-subgraph-001", "version": 1, "state": {"lastGroupId": 4, "lastNodeId": 20, "lastLinkId": 20, "lastRerouteId": 0}, "revision": 0, "config": {}, "name": "local-Text to Image (LongCat-Image)", "inputNode": {"id": -10, "bounding": [-80, 425, 120, 160]}, "outputNode": {"id": -20, "bounding": [1490, 415, 120, 60]}, "inputs": [{"id": "inp-text", "name": "text", "type": "STRING", "linkIds": [10], "label": "prompt", "pos": [20, 445]}, {"id": "inp-width", "name": "width", "type": "INT", "linkIds": [11], "pos": [20, 465]}, {"id": "inp-height", "name": "height", "type": "INT", "linkIds": [12], "pos": [20, 485]}, {"id": "inp-unet", "name": "unet_name", "type": "COMBO", "linkIds": [13], "pos": [20, 505]}, {"id": "inp-clip", "name": "clip_name", "type": "COMBO", "linkIds": [14], "pos": [20, 525]}, {"id": "inp-vae", "name": "vae_name", "type": "COMBO", "linkIds": [15], "pos": [20, 545]}], "outputs": [{"id": "out-image", "name": "IMAGE", "type": "IMAGE", "linkIds": [9], "localized_name": "IMAGE", "pos": [1510, 435]}], "widgets": [], "nodes": [{"id": 1, "type": "UNETLoader", "pos": [110, 200], "size": [270, 82], "flags": {}, "order": 0, "mode": 0, "inputs": [{"name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": 13}, {"name": "weight_dtype", "type": "COMBO", "widget": {"name": "weight_dtype"}, "link": null}], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [1]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "UNETLoader", "models": [{"name": "longcat_image.safetensors", "url": "https://huggingface.co/meituan-longcat/LongCat-Image/resolve/main/transformer/diffusion_pytorch_model.safetensors", "directory": "diffusion_models"}]}, "widgets_values": ["longcat_image.safetensors", "default"]}, {"id": 2, "type": "CLIPLoader", "pos": [110, 330], "size": [270, 106], "flags": {}, "order": 1, "mode": 0, "inputs": [{"name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": 14}, {"name": "type", "type": "COMBO", "widget": {"name": "type"}, "link": null}, {"name": "device", "shape": 7, "type": "COMBO", "widget": {"name": "device"}, "link": null}], "outputs": [{"name": "CLIP", "type": "CLIP", "links": [2, 16]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPLoader", "models": [{"name": "qwen_2.5_vl_7b.safetensors", "url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b.safetensors", "directory": "text_encoders"}]}, "widgets_values": ["qwen_2.5_vl_7b.safetensors", "longcat_image", "default"]}, {"id": 3, "type": "VAELoader", "pos": [110, 480], "size": [270, 58], "flags": {}, "order": 2, "mode": 0, "inputs": [{"name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": 15}], "outputs": [{"name": "VAE", "type": "VAE", "links": [3]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "VAELoader", "models": [{"name": "ae.safetensors", "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors", "directory": "vae"}]}, "widgets_values": ["ae.safetensors"]}, {"id": 4, "type": "CLIPTextEncodeLongCatImage", "pos": [430, 200], "size": [410, 250], "flags": {}, "order": 3, "mode": 0, "inputs": [{"name": "clip", "type": "CLIP", "link": 2}, {"name": "text", "type": "STRING", "widget": {"name": "text"}, "link": 10}, {"name": "guidance", "type": "FLOAT", "widget": {"name": "guidance"}, "link": null}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [4]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPTextEncodeLongCatImage"}, "widgets_values": ["", 4.0]}, {"id": 5, "type": "CLIPTextEncodeLongCatImage", "pos": [430, 510], "size": [410, 120], "flags": {}, "order": 4, "mode": 0, "inputs": [{"name": "clip", "type": "CLIP", "link": 16}, {"name": "text", "type": "STRING", "widget": {"name": "text"}, "link": null}, {"name": "guidance", "type": "FLOAT", "widget": {"name": "guidance"}, "link": null}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [6]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPTextEncodeLongCatImage"}, "widgets_values": ["", 4.0]}, {"id": 10, "type": "CFGRenormLongCatImage", "pos": [880, 160], "size": [280, 26], "flags": {}, "order": 5, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 1}], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [17]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CFGRenormLongCatImage"}, "widgets_values": []}, {"id": 6, "type": "EmptySD3LatentImage", "pos": [110, 630], "size": [260, 106], "flags": {}, "order": 6, "mode": 0, "inputs": [{"name": "width", "type": "INT", "widget": {"name": "width"}, "link": 11}, {"name": "height", "type": "INT", "widget": {"name": "height"}, "link": 12}, {"name": "batch_size", "type": "INT", "widget": {"name": "batch_size"}, "link": null}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [7]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "EmptySD3LatentImage"}, "widgets_values": [768, 1344, 1]}, {"id": 7, "type": "KSampler", "pos": [880, 230], "size": [315, 262], "flags": {}, "order": 7, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 17}, {"name": "positive", "type": "CONDITIONING", "link": 4}, {"name": "negative", "type": "CONDITIONING", "link": 6}, {"name": "latent_image", "type": "LATENT", "link": 7}, {"name": "seed", "type": "INT", "widget": {"name": "seed"}, "link": null}, {"name": "steps", "type": "INT", "widget": {"name": "steps"}, "link": null}, {"name": "cfg", "type": "FLOAT", "widget": {"name": "cfg"}, "link": null}, {"name": "sampler_name", "type": "COMBO", "widget": {"name": "sampler_name"}, "link": null}, {"name": "scheduler", "type": "COMBO", "widget": {"name": "scheduler"}, "link": null}, {"name": "denoise", "type": "FLOAT", "widget": {"name": "denoise"}, "link": null}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [8]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "KSampler"}, "widgets_values": [0, "randomize", 20, 4.0, "euler", "simple", 1.0]}, {"id": 8, "type": "VAEDecode", "pos": [1220, 160], "size": [210, 46], "flags": {}, "order": 8, "mode": 0, "inputs": [{"name": "samples", "type": "LATENT", "link": 8}, {"name": "vae", "type": "VAE", "link": 3}], "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [9]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "VAEDecode"}, "widgets_values": []}], "groups": [{"id": 1, "title": "Image size", "bounding": [100, 560, 290, 200], "color": "#3f789e", "font_size": 24, "flags": {}}, {"id": 2, "title": "Prompt", "bounding": [410, 130, 450, 540], "color": "#3f789e", "font_size": 24, "flags": {}}, {"id": 3, "title": "Models", "bounding": [100, 130, 290, 413], "color": "#3f789e", "font_size": 24, "flags": {}}], "links": [{"id": 1, "origin_id": 1, "origin_slot": 0, "target_id": 10, "target_slot": 0, "type": "MODEL"}, {"id": 2, "origin_id": 2, "origin_slot": 0, "target_id": 4, "target_slot": 0, "type": "CLIP"}, {"id": 3, "origin_id": 3, "origin_slot": 0, "target_id": 8, "target_slot": 1, "type": "VAE"}, {"id": 4, "origin_id": 4, "origin_slot": 0, "target_id": 7, "target_slot": 1, "type": "CONDITIONING"}, {"id": 6, "origin_id": 5, "origin_slot": 0, "target_id": 7, "target_slot": 2, "type": "CONDITIONING"}, {"id": 7, "origin_id": 6, "origin_slot": 0, "target_id": 7, "target_slot": 3, "type": "LATENT"}, {"id": 8, "origin_id": 7, "origin_slot": 0, "target_id": 8, "target_slot": 0, "type": "LATENT"}, {"id": 9, "origin_id": 8, "origin_slot": 0, "target_id": -20, "target_slot": 0, "type": "IMAGE"}, {"id": 10, "origin_id": -10, "origin_slot": 0, "target_id": 4, "target_slot": 1, "type": "STRING"}, {"id": 11, "origin_id": -10, "origin_slot": 1, "target_id": 6, "target_slot": 0, "type": "INT"}, {"id": 12, "origin_id": -10, "origin_slot": 2, "target_id": 6, "target_slot": 1, "type": "INT"}, {"id": 13, "origin_id": -10, "origin_slot": 3, "target_id": 1, "target_slot": 0, "type": "COMBO"}, {"id": 14, "origin_id": -10, "origin_slot": 4, "target_id": 2, "target_slot": 0, "type": "COMBO"}, {"id": 15, "origin_id": -10, "origin_slot": 5, "target_id": 3, "target_slot": 0, "type": "COMBO"}, {"id": 16, "origin_id": 2, "origin_slot": 0, "target_id": 5, "target_slot": 0, "type": "CLIP"}, {"id": 17, "origin_id": 10, "origin_slot": 0, "target_id": 7, "target_slot": 0, "type": "MODEL"}], "extra": {"workflowRendererVersion": "LG"}, "category": "Image generation and editing/Text to image"}]}, "config": {}, "extra": {"frontendVersion": "1.37.10", "workflowRendererVersion": "LG"}, "version": 0.4} From fcf3d9f8be3f1bb52978adddd6cdc50b2e513ca1 Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Mon, 23 Feb 2026 21:36:58 +0100 Subject: [PATCH 06/18] Add model_detection_test.py --- tests-unit/comfy_test/model_detection_test.py | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 tests-unit/comfy_test/model_detection_test.py diff --git a/tests-unit/comfy_test/model_detection_test.py b/tests-unit/comfy_test/model_detection_test.py new file mode 100644 index 00000000000..a9c4189ef24 --- /dev/null +++ b/tests-unit/comfy_test/model_detection_test.py @@ -0,0 +1,125 @@ +import torch +import pytest +from unittest.mock import patch + +from comfy.model_detection import detect_unet_config, model_config_from_unet_config +import comfy.supported_models + + +def _make_longcat_diffusers_sd(): + """Minimal Diffusers-format state dict that triggers the LongCat-Image detection path.""" + sd = {} + H = 3072 # hidden_size (matches real LongCat-Image) + C_IN = 16 + C_CTX = 3584 # context_in_dim that distinguishes LongCat from standard Flux (4096) + + sd["x_embedder.weight"] = torch.empty(H, C_IN * 4) + sd["x_embedder.bias"] = torch.empty(H) + sd["context_embedder.weight"] = torch.empty(H, C_CTX) + sd["context_embedder.bias"] = torch.empty(H) + + sd["time_embed.timestep_embedder.linear_1.weight"] = torch.empty(H, 256) + sd["time_embed.timestep_embedder.linear_1.bias"] = torch.empty(H) + sd["time_embed.timestep_embedder.linear_2.weight"] = torch.empty(H, H) + sd["time_embed.timestep_embedder.linear_2.bias"] = torch.empty(H) + + sd["norm_out.linear.weight"] = torch.empty(2 * H, H) + sd["norm_out.linear.bias"] = torch.empty(2 * H) + sd["proj_out.weight"] = torch.empty(C_IN * 4, H) + sd["proj_out.bias"] = torch.empty(C_IN * 4) + + # Need enough transformer_blocks and single_transformer_blocks for count_blocks + # and for the required_keys check (single_transformer_blocks.10.*) + for i in range(19): + sd[f"transformer_blocks.{i}.attn.to_q.weight"] = torch.empty(H, H) + sd[f"transformer_blocks.{i}.norm1.linear.weight"] = torch.empty(H) + for i in range(38): + sd[f"single_transformer_blocks.{i}.attn.to_q.weight"] = torch.empty(H, H) + sd[f"single_transformer_blocks.{i}.norm.linear.weight"] = torch.empty(H) + + return sd + + +def _make_flux_schnell_comfyui_sd(): + """Minimal ComfyUI-format state dict that triggers the standard Flux detection path.""" + sd = {} + H = 3072 + C_IN = 16 + + sd["img_in.weight"] = torch.empty(H, C_IN * 4) + sd["img_in.bias"] = torch.empty(H) + sd["txt_in.weight"] = torch.empty(H, 4096) + sd["txt_in.bias"] = torch.empty(H) + + sd["double_blocks.0.img_attn.norm.key_norm.weight"] = torch.empty(128) + sd["double_blocks.0.img_attn.qkv.weight"] = torch.empty(3 * H, H) + sd["double_blocks.0.img_mod.lin.weight"] = torch.empty(H, H) + + for i in range(19): + sd[f"double_blocks.{i}.img_attn.norm.key_norm.weight"] = torch.empty(128) + for i in range(38): + sd[f"single_blocks.{i}.modulation.lin.weight"] = torch.empty(H, H) + + return sd + + +class TestModelDetectionSpecificity: + """Verify that model_config_from_unet_config picks the most specific match.""" + + def test_longcat_wins_regardless_of_list_order(self): + """Specificity logic must pick LongCatImage even when FluxSchnell appears first.""" + sd = _make_longcat_diffusers_sd() + unet_config = detect_unet_config(sd, "") + original_models = comfy.supported_models.models + + longcat_cls = comfy.supported_models.LongCatImage + schnell_cls = comfy.supported_models.FluxSchnell + + # Order A: FluxSchnell before LongCatImage + order_a = [schnell_cls, longcat_cls] + # Order B: LongCatImage before FluxSchnell + order_b = [longcat_cls, schnell_cls] + + for label, order in [("schnell-first", order_a), ("longcat-first", order_b)]: + with patch.object(comfy.supported_models, "models", order): + result = model_config_from_unet_config(unet_config, sd) + assert result is not None, f"No match with order {label}" + assert type(result).__name__ == "LongCatImage", ( + f"Expected LongCatImage with order {label}, got {type(result).__name__}" + ) + + def test_longcat_diffusers_detected_as_longcat(self): + sd = _make_longcat_diffusers_sd() + unet_config = detect_unet_config(sd, "") + assert unet_config is not None + assert unet_config["image_model"] == "flux" + assert unet_config["context_in_dim"] == 3584 + assert unet_config["txt_ids_dims"] == [1, 2] + + model_config = model_config_from_unet_config(unet_config, sd) + assert model_config is not None + assert type(model_config).__name__ == "LongCatImage" + + def test_longcat_process_unet_state_dict_converts_keys(self): + sd = _make_longcat_diffusers_sd() + unet_config = detect_unet_config(sd, "") + model_config = model_config_from_unet_config(unet_config, sd) + + converted = model_config.process_unet_state_dict(dict(sd)) + assert "img_in.weight" in converted + assert "img_in.bias" in converted + assert "txt_in.weight" in converted + assert "x_embedder.weight" not in converted + assert "context_embedder.weight" not in converted + + def test_flux_schnell_comfyui_detected_as_flux_schnell(self): + sd = _make_flux_schnell_comfyui_sd() + unet_config = detect_unet_config(sd, "") + assert unet_config is not None + assert unet_config["image_model"] == "flux" + assert unet_config["context_in_dim"] == 4096 + assert unet_config["txt_ids_dims"] == [] + + model_config = model_config_from_unet_config(unet_config, sd) + assert model_config is not None + assert type(model_config).__name__ == "FluxSchnell" From 87d0a15307ad191a46db9d0044dd33eeff72f946 Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Mon, 23 Feb 2026 21:40:53 +0100 Subject: [PATCH 07/18] Reduce git diff. --- comfy/supported_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comfy/supported_models.py b/comfy/supported_models.py index 12b2d3f0fd4..8bc12d2fe35 100644 --- a/comfy/supported_models.py +++ b/comfy/supported_models.py @@ -1824,6 +1824,6 @@ def clip_target(self, state_dict={}): hunyuan_detect = comfy.text_encoders.hunyuan_video.llama_detect(state_dict, "{}qwen25_7b.transformer.".format(pref)) return supported_models_base.ClipTarget(comfy.text_encoders.longcat_image.LongCatImageTokenizer, comfy.text_encoders.longcat_image.te(**hunyuan_detect)) -models = [LotusD, Stable_Zero123, SD15_instructpix2pix, SD15, SD20, SD21UnclipL, SD21UnclipH, SDXL_instructpix2pix, SDXLRefiner, SDXL, SSD1B, KOALA_700M, KOALA_1B, Segmind_Vega, SD_X4Upscaler, Stable_Cascade_C, Stable_Cascade_B, SV3D_u, SV3D_p, SD3, StableAudio, AuraFlow, PixArtAlpha, PixArtSigma, HunyuanDiT, HunyuanDiT1, FluxInpaint, Flux, FluxSchnell, GenmoMochi, LTXV, LTXAV, HunyuanVideo15_SR_Distilled, HunyuanVideo15, HunyuanImage21Refiner, HunyuanImage21, HunyuanVideoSkyreelsI2V, HunyuanVideoI2V, HunyuanVideo, CosmosT2V, CosmosI2V, CosmosT2IPredict2, CosmosI2VPredict2, ZImage, Lumina2, WAN22_T2V, WAN21_T2V, WAN21_I2V, WAN21_FunControl2V, WAN21_Vace, WAN21_Camera, WAN22_Camera, WAN22_S2V, WAN21_HuMo, WAN22_Animate, WAN21_FlowRVS, Hunyuan3Dv2mini, Hunyuan3Dv2, Hunyuan3Dv2_1, HiDream, Chroma, ChromaRadiance, ACEStep, ACEStep15, Omnigen2, QwenImage, LongCatImage, Flux2, Kandinsky5Image, Kandinsky5, Anima] +models = [LotusD, Stable_Zero123, SD15_instructpix2pix, SD15, SD20, SD21UnclipL, SD21UnclipH, SDXL_instructpix2pix, SDXLRefiner, SDXL, SSD1B, KOALA_700M, KOALA_1B, Segmind_Vega, SD_X4Upscaler, Stable_Cascade_C, Stable_Cascade_B, SV3D_u, SV3D_p, SD3, StableAudio, AuraFlow, PixArtAlpha, PixArtSigma, HunyuanDiT, HunyuanDiT1, FluxInpaint, Flux, LongCatImage, FluxSchnell, GenmoMochi, LTXV, LTXAV, HunyuanVideo15_SR_Distilled, HunyuanVideo15, HunyuanImage21Refiner, HunyuanImage21, HunyuanVideoSkyreelsI2V, HunyuanVideoI2V, HunyuanVideo, CosmosT2V, CosmosI2V, CosmosT2IPredict2, CosmosI2VPredict2, ZImage, Lumina2, WAN22_T2V, WAN21_T2V, WAN21_I2V, WAN21_FunControl2V, WAN21_Vace, WAN21_Camera, WAN22_Camera, WAN22_S2V, WAN21_HuMo, WAN22_Animate, WAN21_FlowRVS, Hunyuan3Dv2mini, Hunyuan3Dv2, Hunyuan3Dv2_1, HiDream, Chroma, ChromaRadiance, ACEStep, ACEStep15, Omnigen2, QwenImage, Flux2, Kandinsky5Image, Kandinsky5, Anima] models += [SVD_img2vid] From 4fe507248c9a074311f3414514ca7134b011d3bd Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Mon, 23 Feb 2026 22:00:59 +0100 Subject: [PATCH 08/18] Fix potential shape missmatch in CFGRenormLongCatImage --- comfy_extras/nodes_longcat_image.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/comfy_extras/nodes_longcat_image.py b/comfy_extras/nodes_longcat_image.py index 6e8178cdb3d..59402dfa08e 100644 --- a/comfy_extras/nodes_longcat_image.py +++ b/comfy_extras/nodes_longcat_image.py @@ -1,7 +1,11 @@ +import logging + import torch from typing_extensions import override from comfy_api.latest import ComfyExtension, io +logger = logging.getLogger(__name__) + class CLIPTextEncodeLongCatImage(io.ComfyNode): @classmethod @@ -61,6 +65,10 @@ def cfg_renorm_post(args): B, C, H, W = denoised.shape ps = 2 + if H % ps != 0 or W % ps != 0: + logger.warning(f"CFG Renorm: incompatible shape {H}x{W}, skipping renorm") + return denoised + noise = x - denoised noise_cond = x - cond_denoised From 6445abe8a0431691fb3c104b67cb0eb7d58285a7 Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Mon, 23 Feb 2026 22:10:15 +0100 Subject: [PATCH 09/18] Add a guard if no <|im_start|> token is found. --- comfy/text_encoders/longcat_image.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/comfy/text_encoders/longcat_image.py b/comfy/text_encoders/longcat_image.py index 3f91f7ce025..4560d1a2efa 100644 --- a/comfy/text_encoders/longcat_image.py +++ b/comfy/text_encoders/longcat_image.py @@ -112,10 +112,13 @@ def encode_token_weights(self, token_weight_pairs, template_end=-1): template_end = i count_im_start += 1 - if out.shape[1] > (template_end + 3): - if tok_pairs[template_end + 1][0] == 872: - if tok_pairs[template_end + 2][0] == 198: - template_end += 3 + if out.shape[1] > (template_end + 3): + if tok_pairs[template_end + 1][0] == 872: + if tok_pairs[template_end + 2][0] == 198: + template_end += 3 + + if template_end == -1: + template_end = 0 suffix_start = None for i in range(len(tok_pairs) - 1, -1, -1): From 32c896ada2abe6f5533e123b2adb605af1bd5c2f Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Mon, 23 Feb 2026 22:37:07 +0100 Subject: [PATCH 10/18] Increase LongCat model detection precision. --- comfy/model_detection.py | 2 +- comfy/supported_models.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/comfy/model_detection.py b/comfy/model_detection.py index 58fe514c454..5a3f2e540b2 100644 --- a/comfy/model_detection.py +++ b/comfy/model_detection.py @@ -282,7 +282,7 @@ def detect_unet_config(state_dict, key_prefix, metadata=None): return dit_config - if '{}x_embedder.weight'.format(key_prefix) in state_dict_keys and '{}transformer_blocks.0.attn.to_q.weight'.format(key_prefix) in state_dict_keys and '{}single_transformer_blocks.0.attn.to_q.weight'.format(key_prefix) in state_dict_keys: #LongCat-Image (diffusers format, Flux variant) + if '{}x_embedder.weight'.format(key_prefix) in state_dict_keys and '{}transformer_blocks.0.attn.to_q.weight'.format(key_prefix) in state_dict_keys and '{}single_transformer_blocks.0.attn.to_q.weight'.format(key_prefix) in state_dict_keys and '{}context_embedder.weight'.format(key_prefix) in state_dict_keys and '{}time_embed.timestep_embedder.linear_1.weight'.format(key_prefix) in state_dict_keys: #LongCat-Image (diffusers format, Flux variant) dit_config = {} dit_config["image_model"] = "flux" dit_config["axes_dim"] = [16, 56, 56] diff --git a/comfy/supported_models.py b/comfy/supported_models.py index 8bc12d2fe35..ca25ffdcf11 100644 --- a/comfy/supported_models.py +++ b/comfy/supported_models.py @@ -1691,6 +1691,7 @@ class LongCatImage(supported_models_base.BASE): "x_embedder.weight": None, "context_embedder.weight": None, "single_transformer_blocks.10.attn.to_q.weight": None, + "time_embed.timestep_embedder.linear_1.weight": None, } sampling_settings = { From dfcd0ca160c190d99f7da6c6b87a0c00b56d3d7e Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Tue, 24 Feb 2026 21:44:25 +0100 Subject: [PATCH 11/18] Fix ruff formatting issues. --- comfy/text_encoders/longcat_image.py | 57 +++++++++++++------ tests-unit/comfy_test/model_detection_test.py | 2 - 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/comfy/text_encoders/longcat_image.py b/comfy/text_encoders/longcat_image.py index 4560d1a2efa..882d80901e1 100644 --- a/comfy/text_encoders/longcat_image.py +++ b/comfy/text_encoders/longcat_image.py @@ -9,7 +9,10 @@ QUOTE_PAIRS = [("'", "'"), ('"', '"'), ("\u2018", "\u2019"), ("\u201c", "\u201d")] QUOTE_PATTERN = "|".join( - [re.escape(q1) + r"[^" + re.escape(q1 + q2) + r"]*?" + re.escape(q2) for q1, q2 in QUOTE_PAIRS] + [ + re.escape(q1) + r"[^" + re.escape(q1 + q2) + r"]*?" + re.escape(q2) + for q1, q2 in QUOTE_PAIRS + ] ) WORD_INTERNAL_QUOTE_RE = re.compile(r"[a-zA-Z]+'[a-zA-Z]+") @@ -52,7 +55,7 @@ def tokenize_with_weights(self, text, return_word_ids=False, **kwargs): all_tokens.extend(ids) if len(all_tokens) > self.max_length: - all_tokens = all_tokens[:self.max_length] + all_tokens = all_tokens[: self.max_length] logger.warning(f"Truncated prompt to {self.max_length} tokens") output = [(t, 1.0) for t in all_tokens] @@ -63,27 +66,40 @@ def tokenize_with_weights(self, text, return_word_ids=False, **kwargs): class LongCatImageTokenizer(sd1_clip.SD1Tokenizer): def __init__(self, embedding_directory=None, tokenizer_data={}): - super().__init__(embedding_directory=embedding_directory, tokenizer_data=tokenizer_data, name="qwen25_7b", tokenizer=LongCatImageBaseTokenizer) + super().__init__( + embedding_directory=embedding_directory, + tokenizer_data=tokenizer_data, + name="qwen25_7b", + tokenizer=LongCatImageBaseTokenizer, + ) self.longcat_template_prefix = "<|im_start|>system\nAs an image captioning expert, generate a descriptive text prompt based on an image content, suitable for input to a text-to-image model.<|im_end|>\n<|im_start|>user\n" self.longcat_template_suffix = "<|im_end|>\n<|im_start|>assistant\n" def tokenize_with_weights(self, text, return_word_ids=False, **kwargs): skip_template = False - if text.startswith('<|im_start|>'): + if text.startswith("<|im_start|>"): skip_template = True - if text.startswith('<|start_header_id|>'): + if text.startswith("<|start_header_id|>"): skip_template = True - if text == '': - text = ' ' + if text == "": + text = " " base_tok = getattr(self, "qwen25_7b") if skip_template: - tokens = super().tokenize_with_weights(text, return_word_ids=return_word_ids, disable_weights=True, **kwargs) + tokens = super().tokenize_with_weights( + text, return_word_ids=return_word_ids, disable_weights=True, **kwargs + ) else: - prefix_ids = base_tok.tokenizer(self.longcat_template_prefix, add_special_tokens=False)["input_ids"] - suffix_ids = base_tok.tokenizer(self.longcat_template_suffix, add_special_tokens=False)["input_ids"] - - prompt_tokens = base_tok.tokenize_with_weights(text, return_word_ids=return_word_ids, **kwargs) + prefix_ids = base_tok.tokenizer( + self.longcat_template_prefix, add_special_tokens=False + )["input_ids"] + suffix_ids = base_tok.tokenizer( + self.longcat_template_suffix, add_special_tokens=False + )["input_ids"] + + prompt_tokens = base_tok.tokenize_with_weights( + text, return_word_ids=return_word_ids, **kwargs + ) prompt_pairs = prompt_tokens[0] prefix_pairs = [(t, 1.0) for t in prefix_ids] @@ -97,7 +113,13 @@ def tokenize_with_weights(self, text, return_word_ids=False, **kwargs): class LongCatImageTEModel(sd1_clip.SD1ClipModel): def __init__(self, device="cpu", dtype=None, model_options={}): - super().__init__(device=device, dtype=dtype, name="qwen25_7b", clip_model=Qwen25_7BVLIModel, model_options=model_options) + super().__init__( + device=device, + dtype=dtype, + name="qwen25_7b", + clip_model=Qwen25_7BVLIModel, + model_options=model_options, + ) def encode_token_weights(self, token_weight_pairs, template_end=-1): out, pooled, extra = super().encode_token_weights(token_weight_pairs) @@ -116,9 +138,9 @@ def encode_token_weights(self, token_weight_pairs, template_end=-1): if tok_pairs[template_end + 1][0] == 872: if tok_pairs[template_end + 2][0] == 198: template_end += 3 - + if template_end == -1: - template_end = 0 + template_end = 0 suffix_start = None for i in range(len(tok_pairs) - 1, -1, -1): @@ -141,7 +163,9 @@ def encode_token_weights(self, token_weight_pairs, template_end=-1): out = out[:, :-suffix_len] if "attention_mask" in extra: extra["attention_mask"] = extra["attention_mask"][:, :-suffix_len] - if extra["attention_mask"].sum() == torch.numel(extra["attention_mask"]): + if extra["attention_mask"].sum() == torch.numel( + extra["attention_mask"] + ): extra.pop("attention_mask") return out, pooled, extra @@ -156,4 +180,5 @@ def __init__(self, device="cpu", dtype=None, model_options={}): if dtype_llama is not None: dtype = dtype_llama super().__init__(device=device, dtype=dtype, model_options=model_options) + return LongCatImageTEModel_ diff --git a/tests-unit/comfy_test/model_detection_test.py b/tests-unit/comfy_test/model_detection_test.py index a9c4189ef24..60f247264f5 100644 --- a/tests-unit/comfy_test/model_detection_test.py +++ b/tests-unit/comfy_test/model_detection_test.py @@ -1,5 +1,4 @@ import torch -import pytest from unittest.mock import patch from comfy.model_detection import detect_unet_config, model_config_from_unet_config @@ -70,7 +69,6 @@ def test_longcat_wins_regardless_of_list_order(self): """Specificity logic must pick LongCatImage even when FluxSchnell appears first.""" sd = _make_longcat_diffusers_sd() unet_config = detect_unet_config(sd, "") - original_models = comfy.supported_models.models longcat_cls = comfy.supported_models.LongCatImage schnell_cls = comfy.supported_models.FluxSchnell From adae3b4a488a92264e15acc9042075037dcb6113 Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Tue, 24 Feb 2026 23:13:32 +0100 Subject: [PATCH 12/18] Simplify the logic by using repackaged weights. --- blueprints/Text to Image (LongCat-Image).json | 2 +- comfy/model_detection.py | 32 +---- comfy/supported_models.py | 116 ------------------ tests-unit/comfy_test/model_detection_test.py | 78 ++++++------ 4 files changed, 41 insertions(+), 187 deletions(-) diff --git a/blueprints/Text to Image (LongCat-Image).json b/blueprints/Text to Image (LongCat-Image).json index 1a01ec30aa0..eda14894954 100644 --- a/blueprints/Text to Image (LongCat-Image).json +++ b/blueprints/Text to Image (LongCat-Image).json @@ -1 +1 @@ -{"id": "a7e3b1c0-4f2d-4e8a-9b1c-longcat00001", "revision": 0, "last_node_id": 20, "last_link_id": 20, "nodes": [{"id": 1, "type": "lc-subgraph-001", "pos": [0, 1230], "size": [400, 470], "flags": {}, "order": 0, "mode": 0, "inputs": [{"label": "prompt", "name": "text", "type": "STRING", "widget": {"name": "text"}, "link": null}, {"name": "width", "type": "INT", "widget": {"name": "width"}, "link": null}, {"name": "height", "type": "INT", "widget": {"name": "height"}, "link": null}, {"name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": null}, {"name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": null}, {"name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": null}], "outputs": [{"localized_name": "IMAGE", "name": "IMAGE", "type": "IMAGE", "links": []}], "properties": {"proxyWidgets": [["-1", "text"], ["-1", "width"], ["-1", "height"], ["7", "seed"], ["7", "control_after_generate"], ["-1", "unet_name"], ["-1", "clip_name"], ["-1", "vae_name"]], "cnr_id": "comfy-core", "ver": "0.3.73", "enableTabs": false}, "widgets_values": ["A young Asian woman wearing a yellow knit sweater with a white necklace, sitting with her hands on her knees and a serene expression. The background is a rough brick wall with warm afternoon sunlight.", 768, 1344, null, null, "longcat_image.safetensors", "qwen_2.5_vl_7b.safetensors", "ae.safetensors"]}], "links": [], "groups": [], "definitions": {"subgraphs": [{"id": "lc-subgraph-001", "version": 1, "state": {"lastGroupId": 4, "lastNodeId": 20, "lastLinkId": 20, "lastRerouteId": 0}, "revision": 0, "config": {}, "name": "local-Text to Image (LongCat-Image)", "inputNode": {"id": -10, "bounding": [-80, 425, 120, 160]}, "outputNode": {"id": -20, "bounding": [1490, 415, 120, 60]}, "inputs": [{"id": "inp-text", "name": "text", "type": "STRING", "linkIds": [10], "label": "prompt", "pos": [20, 445]}, {"id": "inp-width", "name": "width", "type": "INT", "linkIds": [11], "pos": [20, 465]}, {"id": "inp-height", "name": "height", "type": "INT", "linkIds": [12], "pos": [20, 485]}, {"id": "inp-unet", "name": "unet_name", "type": "COMBO", "linkIds": [13], "pos": [20, 505]}, {"id": "inp-clip", "name": "clip_name", "type": "COMBO", "linkIds": [14], "pos": [20, 525]}, {"id": "inp-vae", "name": "vae_name", "type": "COMBO", "linkIds": [15], "pos": [20, 545]}], "outputs": [{"id": "out-image", "name": "IMAGE", "type": "IMAGE", "linkIds": [9], "localized_name": "IMAGE", "pos": [1510, 435]}], "widgets": [], "nodes": [{"id": 1, "type": "UNETLoader", "pos": [110, 200], "size": [270, 82], "flags": {}, "order": 0, "mode": 0, "inputs": [{"name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": 13}, {"name": "weight_dtype", "type": "COMBO", "widget": {"name": "weight_dtype"}, "link": null}], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [1]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "UNETLoader", "models": [{"name": "longcat_image.safetensors", "url": "https://huggingface.co/meituan-longcat/LongCat-Image/resolve/main/transformer/diffusion_pytorch_model.safetensors", "directory": "diffusion_models"}]}, "widgets_values": ["longcat_image.safetensors", "default"]}, {"id": 2, "type": "CLIPLoader", "pos": [110, 330], "size": [270, 106], "flags": {}, "order": 1, "mode": 0, "inputs": [{"name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": 14}, {"name": "type", "type": "COMBO", "widget": {"name": "type"}, "link": null}, {"name": "device", "shape": 7, "type": "COMBO", "widget": {"name": "device"}, "link": null}], "outputs": [{"name": "CLIP", "type": "CLIP", "links": [2, 16]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPLoader", "models": [{"name": "qwen_2.5_vl_7b.safetensors", "url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b.safetensors", "directory": "text_encoders"}]}, "widgets_values": ["qwen_2.5_vl_7b.safetensors", "longcat_image", "default"]}, {"id": 3, "type": "VAELoader", "pos": [110, 480], "size": [270, 58], "flags": {}, "order": 2, "mode": 0, "inputs": [{"name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": 15}], "outputs": [{"name": "VAE", "type": "VAE", "links": [3]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "VAELoader", "models": [{"name": "ae.safetensors", "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors", "directory": "vae"}]}, "widgets_values": ["ae.safetensors"]}, {"id": 4, "type": "CLIPTextEncodeLongCatImage", "pos": [430, 200], "size": [410, 250], "flags": {}, "order": 3, "mode": 0, "inputs": [{"name": "clip", "type": "CLIP", "link": 2}, {"name": "text", "type": "STRING", "widget": {"name": "text"}, "link": 10}, {"name": "guidance", "type": "FLOAT", "widget": {"name": "guidance"}, "link": null}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [4]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPTextEncodeLongCatImage"}, "widgets_values": ["", 4.0]}, {"id": 5, "type": "CLIPTextEncodeLongCatImage", "pos": [430, 510], "size": [410, 120], "flags": {}, "order": 4, "mode": 0, "inputs": [{"name": "clip", "type": "CLIP", "link": 16}, {"name": "text", "type": "STRING", "widget": {"name": "text"}, "link": null}, {"name": "guidance", "type": "FLOAT", "widget": {"name": "guidance"}, "link": null}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [6]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPTextEncodeLongCatImage"}, "widgets_values": ["", 4.0]}, {"id": 10, "type": "CFGRenormLongCatImage", "pos": [880, 160], "size": [280, 26], "flags": {}, "order": 5, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 1}], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [17]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CFGRenormLongCatImage"}, "widgets_values": []}, {"id": 6, "type": "EmptySD3LatentImage", "pos": [110, 630], "size": [260, 106], "flags": {}, "order": 6, "mode": 0, "inputs": [{"name": "width", "type": "INT", "widget": {"name": "width"}, "link": 11}, {"name": "height", "type": "INT", "widget": {"name": "height"}, "link": 12}, {"name": "batch_size", "type": "INT", "widget": {"name": "batch_size"}, "link": null}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [7]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "EmptySD3LatentImage"}, "widgets_values": [768, 1344, 1]}, {"id": 7, "type": "KSampler", "pos": [880, 230], "size": [315, 262], "flags": {}, "order": 7, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 17}, {"name": "positive", "type": "CONDITIONING", "link": 4}, {"name": "negative", "type": "CONDITIONING", "link": 6}, {"name": "latent_image", "type": "LATENT", "link": 7}, {"name": "seed", "type": "INT", "widget": {"name": "seed"}, "link": null}, {"name": "steps", "type": "INT", "widget": {"name": "steps"}, "link": null}, {"name": "cfg", "type": "FLOAT", "widget": {"name": "cfg"}, "link": null}, {"name": "sampler_name", "type": "COMBO", "widget": {"name": "sampler_name"}, "link": null}, {"name": "scheduler", "type": "COMBO", "widget": {"name": "scheduler"}, "link": null}, {"name": "denoise", "type": "FLOAT", "widget": {"name": "denoise"}, "link": null}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [8]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "KSampler"}, "widgets_values": [0, "randomize", 20, 4.0, "euler", "simple", 1.0]}, {"id": 8, "type": "VAEDecode", "pos": [1220, 160], "size": [210, 46], "flags": {}, "order": 8, "mode": 0, "inputs": [{"name": "samples", "type": "LATENT", "link": 8}, {"name": "vae", "type": "VAE", "link": 3}], "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [9]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "VAEDecode"}, "widgets_values": []}], "groups": [{"id": 1, "title": "Image size", "bounding": [100, 560, 290, 200], "color": "#3f789e", "font_size": 24, "flags": {}}, {"id": 2, "title": "Prompt", "bounding": [410, 130, 450, 540], "color": "#3f789e", "font_size": 24, "flags": {}}, {"id": 3, "title": "Models", "bounding": [100, 130, 290, 413], "color": "#3f789e", "font_size": 24, "flags": {}}], "links": [{"id": 1, "origin_id": 1, "origin_slot": 0, "target_id": 10, "target_slot": 0, "type": "MODEL"}, {"id": 2, "origin_id": 2, "origin_slot": 0, "target_id": 4, "target_slot": 0, "type": "CLIP"}, {"id": 3, "origin_id": 3, "origin_slot": 0, "target_id": 8, "target_slot": 1, "type": "VAE"}, {"id": 4, "origin_id": 4, "origin_slot": 0, "target_id": 7, "target_slot": 1, "type": "CONDITIONING"}, {"id": 6, "origin_id": 5, "origin_slot": 0, "target_id": 7, "target_slot": 2, "type": "CONDITIONING"}, {"id": 7, "origin_id": 6, "origin_slot": 0, "target_id": 7, "target_slot": 3, "type": "LATENT"}, {"id": 8, "origin_id": 7, "origin_slot": 0, "target_id": 8, "target_slot": 0, "type": "LATENT"}, {"id": 9, "origin_id": 8, "origin_slot": 0, "target_id": -20, "target_slot": 0, "type": "IMAGE"}, {"id": 10, "origin_id": -10, "origin_slot": 0, "target_id": 4, "target_slot": 1, "type": "STRING"}, {"id": 11, "origin_id": -10, "origin_slot": 1, "target_id": 6, "target_slot": 0, "type": "INT"}, {"id": 12, "origin_id": -10, "origin_slot": 2, "target_id": 6, "target_slot": 1, "type": "INT"}, {"id": 13, "origin_id": -10, "origin_slot": 3, "target_id": 1, "target_slot": 0, "type": "COMBO"}, {"id": 14, "origin_id": -10, "origin_slot": 4, "target_id": 2, "target_slot": 0, "type": "COMBO"}, {"id": 15, "origin_id": -10, "origin_slot": 5, "target_id": 3, "target_slot": 0, "type": "COMBO"}, {"id": 16, "origin_id": 2, "origin_slot": 0, "target_id": 5, "target_slot": 0, "type": "CLIP"}, {"id": 17, "origin_id": 10, "origin_slot": 0, "target_id": 7, "target_slot": 0, "type": "MODEL"}], "extra": {"workflowRendererVersion": "LG"}, "category": "Image generation and editing/Text to image"}]}, "config": {}, "extra": {"frontendVersion": "1.37.10", "workflowRendererVersion": "LG"}, "version": 0.4} +{"id": "a7e3b1c0-4f2d-4e8a-9b1c-longcat00001", "revision": 0, "last_node_id": 20, "last_link_id": 20, "nodes": [{"id": 1, "type": "lc-subgraph-001", "pos": [0, 1230], "size": [400, 470], "flags": {}, "order": 0, "mode": 0, "inputs": [{"label": "prompt", "name": "text", "type": "STRING", "widget": {"name": "text"}, "link": null}, {"name": "width", "type": "INT", "widget": {"name": "width"}, "link": null}, {"name": "height", "type": "INT", "widget": {"name": "height"}, "link": null}, {"name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": null}, {"name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": null}, {"name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": null}], "outputs": [{"localized_name": "IMAGE", "name": "IMAGE", "type": "IMAGE", "links": []}], "properties": {"proxyWidgets": [["-1", "text"], ["-1", "width"], ["-1", "height"], ["7", "seed"], ["7", "control_after_generate"], ["-1", "unet_name"], ["-1", "clip_name"], ["-1", "vae_name"]], "cnr_id": "comfy-core", "ver": "0.3.73", "enableTabs": false}, "widgets_values": ["A young Asian woman wearing a yellow knit sweater with a white necklace, sitting with her hands on her knees and a serene expression. The background is a rough brick wall with warm afternoon sunlight.", 768, 1344, null, null, "longcat_image_bf16.safetensors", "qwen_2.5_vl_7b.safetensors", "ae.safetensors"]}], "links": [], "groups": [], "definitions": {"subgraphs": [{"id": "lc-subgraph-001", "version": 1, "state": {"lastGroupId": 4, "lastNodeId": 20, "lastLinkId": 20, "lastRerouteId": 0}, "revision": 0, "config": {}, "name": "local-Text to Image (LongCat-Image)", "inputNode": {"id": -10, "bounding": [-80, 425, 120, 160]}, "outputNode": {"id": -20, "bounding": [1490, 415, 120, 60]}, "inputs": [{"id": "inp-text", "name": "text", "type": "STRING", "linkIds": [10], "label": "prompt", "pos": [20, 445]}, {"id": "inp-width", "name": "width", "type": "INT", "linkIds": [11], "pos": [20, 465]}, {"id": "inp-height", "name": "height", "type": "INT", "linkIds": [12], "pos": [20, 485]}, {"id": "inp-unet", "name": "unet_name", "type": "COMBO", "linkIds": [13], "pos": [20, 505]}, {"id": "inp-clip", "name": "clip_name", "type": "COMBO", "linkIds": [14], "pos": [20, 525]}, {"id": "inp-vae", "name": "vae_name", "type": "COMBO", "linkIds": [15], "pos": [20, 545]}], "outputs": [{"id": "out-image", "name": "IMAGE", "type": "IMAGE", "linkIds": [9], "localized_name": "IMAGE", "pos": [1510, 435]}], "widgets": [], "nodes": [{"id": 1, "type": "UNETLoader", "pos": [110, 200], "size": [270, 82], "flags": {}, "order": 0, "mode": 0, "inputs": [{"name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": 13}, {"name": "weight_dtype", "type": "COMBO", "widget": {"name": "weight_dtype"}, "link": null}], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [1]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "UNETLoader", "models": [{"name": "longcat_image_bf16.safetensors", "url": "https://huggingface.co/TalmajM/LongCat-Image_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/longcat_image_bf16.safetensors", "directory": "unet"}]}, "widgets_values": ["longcat_image_bf16.safetensors", "default"]}, {"id": 2, "type": "CLIPLoader", "pos": [110, 330], "size": [270, 106], "flags": {}, "order": 1, "mode": 0, "inputs": [{"name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": 14}, {"name": "type", "type": "COMBO", "widget": {"name": "type"}, "link": null}, {"name": "device", "shape": 7, "type": "COMBO", "widget": {"name": "device"}, "link": null}], "outputs": [{"name": "CLIP", "type": "CLIP", "links": [2, 16]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPLoader", "models": [{"name": "qwen_2.5_vl_7b.safetensors", "url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b.safetensors", "directory": "text_encoders"}]}, "widgets_values": ["qwen_2.5_vl_7b.safetensors", "longcat_image", "default"]}, {"id": 3, "type": "VAELoader", "pos": [110, 480], "size": [270, 58], "flags": {}, "order": 2, "mode": 0, "inputs": [{"name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": 15}], "outputs": [{"name": "VAE", "type": "VAE", "links": [3]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "VAELoader", "models": [{"name": "ae.safetensors", "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors", "directory": "vae"}]}, "widgets_values": ["ae.safetensors"]}, {"id": 4, "type": "CLIPTextEncodeLongCatImage", "pos": [430, 200], "size": [410, 250], "flags": {}, "order": 3, "mode": 0, "inputs": [{"name": "clip", "type": "CLIP", "link": 2}, {"name": "text", "type": "STRING", "widget": {"name": "text"}, "link": 10}, {"name": "guidance", "type": "FLOAT", "widget": {"name": "guidance"}, "link": null}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [4]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPTextEncodeLongCatImage"}, "widgets_values": ["", 4.0]}, {"id": 5, "type": "CLIPTextEncodeLongCatImage", "pos": [430, 510], "size": [410, 120], "flags": {}, "order": 4, "mode": 0, "inputs": [{"name": "clip", "type": "CLIP", "link": 16}, {"name": "text", "type": "STRING", "widget": {"name": "text"}, "link": null}, {"name": "guidance", "type": "FLOAT", "widget": {"name": "guidance"}, "link": null}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [6]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPTextEncodeLongCatImage"}, "widgets_values": ["", 4.0]}, {"id": 10, "type": "CFGRenormLongCatImage", "pos": [880, 160], "size": [280, 26], "flags": {}, "order": 5, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 1}], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [17]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CFGRenormLongCatImage"}, "widgets_values": []}, {"id": 6, "type": "EmptySD3LatentImage", "pos": [110, 630], "size": [260, 106], "flags": {}, "order": 6, "mode": 0, "inputs": [{"name": "width", "type": "INT", "widget": {"name": "width"}, "link": 11}, {"name": "height", "type": "INT", "widget": {"name": "height"}, "link": 12}, {"name": "batch_size", "type": "INT", "widget": {"name": "batch_size"}, "link": null}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [7]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "EmptySD3LatentImage"}, "widgets_values": [768, 1344, 1]}, {"id": 7, "type": "KSampler", "pos": [880, 230], "size": [315, 262], "flags": {}, "order": 7, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 17}, {"name": "positive", "type": "CONDITIONING", "link": 4}, {"name": "negative", "type": "CONDITIONING", "link": 6}, {"name": "latent_image", "type": "LATENT", "link": 7}, {"name": "seed", "type": "INT", "widget": {"name": "seed"}, "link": null}, {"name": "steps", "type": "INT", "widget": {"name": "steps"}, "link": null}, {"name": "cfg", "type": "FLOAT", "widget": {"name": "cfg"}, "link": null}, {"name": "sampler_name", "type": "COMBO", "widget": {"name": "sampler_name"}, "link": null}, {"name": "scheduler", "type": "COMBO", "widget": {"name": "scheduler"}, "link": null}, {"name": "denoise", "type": "FLOAT", "widget": {"name": "denoise"}, "link": null}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [8]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "KSampler"}, "widgets_values": [0, "randomize", 20, 4.0, "euler", "simple", 1.0]}, {"id": 8, "type": "VAEDecode", "pos": [1220, 160], "size": [210, 46], "flags": {}, "order": 8, "mode": 0, "inputs": [{"name": "samples", "type": "LATENT", "link": 8}, {"name": "vae", "type": "VAE", "link": 3}], "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [9]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "VAEDecode"}, "widgets_values": []}], "groups": [{"id": 1, "title": "Image size", "bounding": [100, 560, 290, 200], "color": "#3f789e", "font_size": 24, "flags": {}}, {"id": 2, "title": "Prompt", "bounding": [410, 130, 450, 540], "color": "#3f789e", "font_size": 24, "flags": {}}, {"id": 3, "title": "Models", "bounding": [100, 130, 290, 413], "color": "#3f789e", "font_size": 24, "flags": {}}], "links": [{"id": 1, "origin_id": 1, "origin_slot": 0, "target_id": 10, "target_slot": 0, "type": "MODEL"}, {"id": 2, "origin_id": 2, "origin_slot": 0, "target_id": 4, "target_slot": 0, "type": "CLIP"}, {"id": 3, "origin_id": 3, "origin_slot": 0, "target_id": 8, "target_slot": 1, "type": "VAE"}, {"id": 4, "origin_id": 4, "origin_slot": 0, "target_id": 7, "target_slot": 1, "type": "CONDITIONING"}, {"id": 6, "origin_id": 5, "origin_slot": 0, "target_id": 7, "target_slot": 2, "type": "CONDITIONING"}, {"id": 7, "origin_id": 6, "origin_slot": 0, "target_id": 7, "target_slot": 3, "type": "LATENT"}, {"id": 8, "origin_id": 7, "origin_slot": 0, "target_id": 8, "target_slot": 0, "type": "LATENT"}, {"id": 9, "origin_id": 8, "origin_slot": 0, "target_id": -20, "target_slot": 0, "type": "IMAGE"}, {"id": 10, "origin_id": -10, "origin_slot": 0, "target_id": 4, "target_slot": 1, "type": "STRING"}, {"id": 11, "origin_id": -10, "origin_slot": 1, "target_id": 6, "target_slot": 0, "type": "INT"}, {"id": 12, "origin_id": -10, "origin_slot": 2, "target_id": 6, "target_slot": 1, "type": "INT"}, {"id": 13, "origin_id": -10, "origin_slot": 3, "target_id": 1, "target_slot": 0, "type": "COMBO"}, {"id": 14, "origin_id": -10, "origin_slot": 4, "target_id": 2, "target_slot": 0, "type": "COMBO"}, {"id": 15, "origin_id": -10, "origin_slot": 5, "target_id": 3, "target_slot": 0, "type": "COMBO"}, {"id": 16, "origin_id": 2, "origin_slot": 0, "target_id": 5, "target_slot": 0, "type": "CLIP"}, {"id": 17, "origin_id": 10, "origin_slot": 0, "target_id": 7, "target_slot": 0, "type": "MODEL"}], "extra": {"workflowRendererVersion": "LG"}, "category": "Image generation and editing/Text to image"}]}, "config": {}, "extra": {"frontendVersion": "1.37.10", "workflowRendererVersion": "LG"}, "version": 0.4} diff --git a/comfy/model_detection.py b/comfy/model_detection.py index 5a3f2e540b2..4206b831bd5 100644 --- a/comfy/model_detection.py +++ b/comfy/model_detection.py @@ -279,36 +279,8 @@ def detect_unet_config(state_dict, key_prefix, metadata=None): dit_config["txt_norm"] = any_suffix_in(state_dict_keys, key_prefix, 'txt_norm.', ["weight", "scale"]) if dit_config["yak_mlp"] and dit_config["txt_norm"]: # Ovis model dit_config["txt_ids_dims"] = [1, 2] - - return dit_config - - if '{}x_embedder.weight'.format(key_prefix) in state_dict_keys and '{}transformer_blocks.0.attn.to_q.weight'.format(key_prefix) in state_dict_keys and '{}single_transformer_blocks.0.attn.to_q.weight'.format(key_prefix) in state_dict_keys and '{}context_embedder.weight'.format(key_prefix) in state_dict_keys and '{}time_embed.timestep_embedder.linear_1.weight'.format(key_prefix) in state_dict_keys: #LongCat-Image (diffusers format, Flux variant) - dit_config = {} - dit_config["image_model"] = "flux" - dit_config["axes_dim"] = [16, 56, 56] - dit_config["theta"] = 10000 - dit_config["qkv_bias"] = True - dit_config["txt_ids_dims"] = [1, 2] - - w = state_dict['{}x_embedder.weight'.format(key_prefix)] - dit_config["hidden_size"] = w.shape[0] - dit_config["in_channels"] = w.shape[1] // 4 - dit_config["out_channels"] = dit_config["in_channels"] - dit_config["patch_size"] = 2 - - ctx_key = '{}context_embedder.weight'.format(key_prefix) - if ctx_key in state_dict_keys: - dit_config["context_in_dim"] = state_dict[ctx_key].shape[1] - else: - dit_config["context_in_dim"] = 3584 - - dit_config["vec_in_dim"] = None - dit_config["guidance_embed"] = False - dit_config["mlp_ratio"] = 4.0 - dit_config["num_heads"] = dit_config["hidden_size"] // sum(dit_config["axes_dim"]) - - dit_config["depth"] = count_blocks(state_dict_keys, '{}transformer_blocks.'.format(key_prefix) + '{}.') - dit_config["depth_single_blocks"] = count_blocks(state_dict_keys, '{}single_transformer_blocks.'.format(key_prefix) + '{}.') + if dit_config.get("context_in_dim") == 3584 and dit_config["vec_in_dim"] is None: # LongCat-Image + dit_config["txt_ids_dims"] = [1, 2] return dit_config diff --git a/comfy/supported_models.py b/comfy/supported_models.py index ca25ffdcf11..eebbee80adb 100644 --- a/comfy/supported_models.py +++ b/comfy/supported_models.py @@ -1687,13 +1687,6 @@ class LongCatImage(supported_models_base.BASE): "txt_ids_dims": [1, 2], } - required_keys = { - "x_embedder.weight": None, - "context_embedder.weight": None, - "single_transformer_blocks.10.attn.to_q.weight": None, - "time_embed.timestep_embedder.linear_1.weight": None, - } - sampling_settings = { } @@ -1707,115 +1700,6 @@ class LongCatImage(supported_models_base.BASE): vae_key_prefix = ["vae."] text_encoder_key_prefix = ["text_encoders."] - def process_unet_state_dict(self, state_dict): - out_sd = {} - double_q, double_k, double_v = {}, {}, {} - double_tq, double_tk, double_tv = {}, {}, {} - single_q, single_k, single_v, single_mlp = {}, {}, {}, {} - - for k, v in state_dict.items(): - if k.startswith("transformer_blocks."): - idx = k.split(".")[1] - rest = ".".join(k.split(".")[2:]) - prefix = "double_blocks.{}.".format(idx) - - if rest.startswith("norm1.linear."): - out_sd[prefix + "img_mod.lin." + rest.split(".")[-1]] = v - elif rest.startswith("norm1_context.linear."): - out_sd[prefix + "txt_mod.lin." + rest.split(".")[-1]] = v - elif rest.startswith("attn.to_q."): - double_q[idx + "." + rest.split(".")[-1]] = v - elif rest.startswith("attn.to_k."): - double_k[idx + "." + rest.split(".")[-1]] = v - elif rest.startswith("attn.to_v."): - double_v[idx + "." + rest.split(".")[-1]] = v - elif rest == "attn.norm_q.weight": - out_sd[prefix + "img_attn.norm.query_norm.weight"] = v - elif rest == "attn.norm_k.weight": - out_sd[prefix + "img_attn.norm.key_norm.weight"] = v - elif rest.startswith("attn.to_out.0."): - out_sd[prefix + "img_attn.proj." + rest.split(".")[-1]] = v - elif rest.startswith("attn.add_q_proj."): - double_tq[idx + "." + rest.split(".")[-1]] = v - elif rest.startswith("attn.add_k_proj."): - double_tk[idx + "." + rest.split(".")[-1]] = v - elif rest.startswith("attn.add_v_proj."): - double_tv[idx + "." + rest.split(".")[-1]] = v - elif rest == "attn.norm_added_q.weight": - out_sd[prefix + "txt_attn.norm.query_norm.weight"] = v - elif rest == "attn.norm_added_k.weight": - out_sd[prefix + "txt_attn.norm.key_norm.weight"] = v - elif rest.startswith("attn.to_add_out."): - out_sd[prefix + "txt_attn.proj." + rest.split(".")[-1]] = v - elif rest.startswith("ff.net.0.proj."): - out_sd[prefix + "img_mlp.0." + rest.split(".")[-1]] = v - elif rest.startswith("ff.net.2."): - out_sd[prefix + "img_mlp.2." + rest.split(".")[-1]] = v - elif rest.startswith("ff_context.net.0.proj."): - out_sd[prefix + "txt_mlp.0." + rest.split(".")[-1]] = v - elif rest.startswith("ff_context.net.2."): - out_sd[prefix + "txt_mlp.2." + rest.split(".")[-1]] = v - else: - out_sd["double_blocks.{}.{}".format(idx, rest)] = v - - elif k.startswith("single_transformer_blocks."): - idx = k.split(".")[1] - rest = ".".join(k.split(".")[2:]) - prefix = "single_blocks.{}.".format(idx) - - if rest.startswith("norm.linear."): - out_sd[prefix + "modulation.lin." + rest.split(".")[-1]] = v - elif rest.startswith("attn.to_q."): - single_q[idx + "." + rest.split(".")[-1]] = v - elif rest.startswith("attn.to_k."): - single_k[idx + "." + rest.split(".")[-1]] = v - elif rest.startswith("attn.to_v."): - single_v[idx + "." + rest.split(".")[-1]] = v - elif rest == "attn.norm_q.weight": - out_sd[prefix + "norm.query_norm.weight"] = v - elif rest == "attn.norm_k.weight": - out_sd[prefix + "norm.key_norm.weight"] = v - elif rest.startswith("proj_mlp."): - single_mlp[idx + "." + rest.split(".")[-1]] = v - elif rest.startswith("proj_out."): - out_sd[prefix + "linear2." + rest.split(".")[-1]] = v - else: - out_sd["single_blocks.{}.{}".format(idx, rest)] = v - - elif k == "x_embedder.weight" or k == "x_embedder.bias": - out_sd["img_in." + k.split(".")[-1]] = v - elif k == "context_embedder.weight" or k == "context_embedder.bias": - out_sd["txt_in." + k.split(".")[-1]] = v - elif k.startswith("time_embed.timestep_embedder.linear_1."): - out_sd["time_in.in_layer." + k.split(".")[-1]] = v - elif k.startswith("time_embed.timestep_embedder.linear_2."): - out_sd["time_in.out_layer." + k.split(".")[-1]] = v - elif k.startswith("norm_out.linear."): - # HF AdaLayerNormContinuous stores [scale | shift] but ComfyUI - # LastLayer expects [shift | scale], so swap the two halves. - half = v.shape[0] // 2 - v = torch.cat([v[half:], v[:half]], dim=0) - out_sd["final_layer.adaLN_modulation.1." + k.split(".")[-1]] = v - elif k == "proj_out.weight" or k == "proj_out.bias": - out_sd["final_layer.linear." + k.split(".")[-1]] = v - else: - out_sd[k] = v - - for suffix in ["weight", "bias"]: - for idx in sorted(set(x.split(".")[0] for x in double_q)): - qk = idx + "." + suffix - if qk in double_q and qk in double_k and qk in double_v: - out_sd["double_blocks.{}.img_attn.qkv.{}".format(idx, suffix)] = torch.cat([double_q[qk], double_k[qk], double_v[qk]], dim=0) - if qk in double_tq and qk in double_tk and qk in double_tv: - out_sd["double_blocks.{}.txt_attn.qkv.{}".format(idx, suffix)] = torch.cat([double_tq[qk], double_tk[qk], double_tv[qk]], dim=0) - - for idx in sorted(set(x.split(".")[0] for x in single_q)): - qk = idx + "." + suffix - if qk in single_q and qk in single_k and qk in single_v and qk in single_mlp: - out_sd["single_blocks.{}.linear1.{}".format(idx, suffix)] = torch.cat([single_q[qk], single_k[qk], single_v[qk], single_mlp[qk]], dim=0) - - return out_sd - def get_model(self, state_dict, prefix="", device=None): out = model_base.LongCatImage(self, device=device) return out diff --git a/tests-unit/comfy_test/model_detection_test.py b/tests-unit/comfy_test/model_detection_test.py index 60f247264f5..e2854274455 100644 --- a/tests-unit/comfy_test/model_detection_test.py +++ b/tests-unit/comfy_test/model_detection_test.py @@ -5,42 +5,40 @@ import comfy.supported_models -def _make_longcat_diffusers_sd(): - """Minimal Diffusers-format state dict that triggers the LongCat-Image detection path.""" +def _make_longcat_comfyui_sd(): + """Minimal ComfyUI-format state dict for pre-converted LongCat-Image weights.""" sd = {} - H = 3072 # hidden_size (matches real LongCat-Image) + H = 3072 C_IN = 16 - C_CTX = 3584 # context_in_dim that distinguishes LongCat from standard Flux (4096) + C_CTX = 3584 - sd["x_embedder.weight"] = torch.empty(H, C_IN * 4) - sd["x_embedder.bias"] = torch.empty(H) - sd["context_embedder.weight"] = torch.empty(H, C_CTX) - sd["context_embedder.bias"] = torch.empty(H) + sd["img_in.weight"] = torch.empty(H, C_IN * 4) + sd["img_in.bias"] = torch.empty(H) + sd["txt_in.weight"] = torch.empty(H, C_CTX) + sd["txt_in.bias"] = torch.empty(H) - sd["time_embed.timestep_embedder.linear_1.weight"] = torch.empty(H, 256) - sd["time_embed.timestep_embedder.linear_1.bias"] = torch.empty(H) - sd["time_embed.timestep_embedder.linear_2.weight"] = torch.empty(H, H) - sd["time_embed.timestep_embedder.linear_2.bias"] = torch.empty(H) + sd["time_in.in_layer.weight"] = torch.empty(H, 256) + sd["time_in.in_layer.bias"] = torch.empty(H) + sd["time_in.out_layer.weight"] = torch.empty(H, H) + sd["time_in.out_layer.bias"] = torch.empty(H) - sd["norm_out.linear.weight"] = torch.empty(2 * H, H) - sd["norm_out.linear.bias"] = torch.empty(2 * H) - sd["proj_out.weight"] = torch.empty(C_IN * 4, H) - sd["proj_out.bias"] = torch.empty(C_IN * 4) + sd["final_layer.adaLN_modulation.1.weight"] = torch.empty(2 * H, H) + sd["final_layer.adaLN_modulation.1.bias"] = torch.empty(2 * H) + sd["final_layer.linear.weight"] = torch.empty(C_IN * 4, H) + sd["final_layer.linear.bias"] = torch.empty(C_IN * 4) - # Need enough transformer_blocks and single_transformer_blocks for count_blocks - # and for the required_keys check (single_transformer_blocks.10.*) for i in range(19): - sd[f"transformer_blocks.{i}.attn.to_q.weight"] = torch.empty(H, H) - sd[f"transformer_blocks.{i}.norm1.linear.weight"] = torch.empty(H) + sd[f"double_blocks.{i}.img_attn.norm.key_norm.weight"] = torch.empty(128) + sd[f"double_blocks.{i}.img_attn.qkv.weight"] = torch.empty(3 * H, H) + sd[f"double_blocks.{i}.img_mod.lin.weight"] = torch.empty(H, H) for i in range(38): - sd[f"single_transformer_blocks.{i}.attn.to_q.weight"] = torch.empty(H, H) - sd[f"single_transformer_blocks.{i}.norm.linear.weight"] = torch.empty(H) + sd[f"single_blocks.{i}.modulation.lin.weight"] = torch.empty(H, H) return sd def _make_flux_schnell_comfyui_sd(): - """Minimal ComfyUI-format state dict that triggers the standard Flux detection path.""" + """Minimal ComfyUI-format state dict for standard Flux Schnell.""" sd = {} H = 3072 C_IN = 16 @@ -67,18 +65,16 @@ class TestModelDetectionSpecificity: def test_longcat_wins_regardless_of_list_order(self): """Specificity logic must pick LongCatImage even when FluxSchnell appears first.""" - sd = _make_longcat_diffusers_sd() + sd = _make_longcat_comfyui_sd() unet_config = detect_unet_config(sd, "") longcat_cls = comfy.supported_models.LongCatImage schnell_cls = comfy.supported_models.FluxSchnell - # Order A: FluxSchnell before LongCatImage - order_a = [schnell_cls, longcat_cls] - # Order B: LongCatImage before FluxSchnell - order_b = [longcat_cls, schnell_cls] - - for label, order in [("schnell-first", order_a), ("longcat-first", order_b)]: + for label, order in [ + ("schnell-first", [schnell_cls, longcat_cls]), + ("longcat-first", [longcat_cls, schnell_cls]), + ]: with patch.object(comfy.supported_models, "models", order): result = model_config_from_unet_config(unet_config, sd) assert result is not None, f"No match with order {label}" @@ -86,29 +82,31 @@ def test_longcat_wins_regardless_of_list_order(self): f"Expected LongCatImage with order {label}, got {type(result).__name__}" ) - def test_longcat_diffusers_detected_as_longcat(self): - sd = _make_longcat_diffusers_sd() + def test_longcat_comfyui_detected_as_longcat(self): + sd = _make_longcat_comfyui_sd() unet_config = detect_unet_config(sd, "") assert unet_config is not None assert unet_config["image_model"] == "flux" assert unet_config["context_in_dim"] == 3584 + assert unet_config["vec_in_dim"] is None + assert unet_config["guidance_embed"] is False assert unet_config["txt_ids_dims"] == [1, 2] model_config = model_config_from_unet_config(unet_config, sd) assert model_config is not None assert type(model_config).__name__ == "LongCatImage" - def test_longcat_process_unet_state_dict_converts_keys(self): - sd = _make_longcat_diffusers_sd() + def test_longcat_comfyui_keys_pass_through_unchanged(self): + """Pre-converted weights should not be transformed by process_unet_state_dict.""" + sd = _make_longcat_comfyui_sd() unet_config = detect_unet_config(sd, "") model_config = model_config_from_unet_config(unet_config, sd) - converted = model_config.process_unet_state_dict(dict(sd)) - assert "img_in.weight" in converted - assert "img_in.bias" in converted - assert "txt_in.weight" in converted - assert "x_embedder.weight" not in converted - assert "context_embedder.weight" not in converted + processed = model_config.process_unet_state_dict(dict(sd)) + assert "img_in.weight" in processed + assert "txt_in.weight" in processed + assert "time_in.in_layer.weight" in processed + assert "final_layer.linear.weight" in processed def test_flux_schnell_comfyui_detected_as_flux_schnell(self): sd = _make_flux_schnell_comfyui_sd() From 0ee3231db2677c7cb63af1cb2574ee05634f6297 Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Wed, 25 Feb 2026 21:09:28 +0100 Subject: [PATCH 13/18] Put LongCat-Image before FluxSchnell for correct selection. --- tests-unit/comfy_test/model_detection_test.py | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/tests-unit/comfy_test/model_detection_test.py b/tests-unit/comfy_test/model_detection_test.py index e2854274455..fdde8994a56 100644 --- a/tests-unit/comfy_test/model_detection_test.py +++ b/tests-unit/comfy_test/model_detection_test.py @@ -1,5 +1,4 @@ import torch -from unittest.mock import patch from comfy.model_detection import detect_unet_config, model_config_from_unet_config import comfy.supported_models @@ -60,27 +59,19 @@ def _make_flux_schnell_comfyui_sd(): return sd -class TestModelDetectionSpecificity: - """Verify that model_config_from_unet_config picks the most specific match.""" +class TestModelDetection: + """Verify that first-match model detection selects the correct model + based on list ordering and unet_config specificity.""" - def test_longcat_wins_regardless_of_list_order(self): - """Specificity logic must pick LongCatImage even when FluxSchnell appears first.""" - sd = _make_longcat_comfyui_sd() - unet_config = detect_unet_config(sd, "") - - longcat_cls = comfy.supported_models.LongCatImage - schnell_cls = comfy.supported_models.FluxSchnell - - for label, order in [ - ("schnell-first", [schnell_cls, longcat_cls]), - ("longcat-first", [longcat_cls, schnell_cls]), - ]: - with patch.object(comfy.supported_models, "models", order): - result = model_config_from_unet_config(unet_config, sd) - assert result is not None, f"No match with order {label}" - assert type(result).__name__ == "LongCatImage", ( - f"Expected LongCatImage with order {label}, got {type(result).__name__}" - ) + def test_longcat_before_schnell_in_models_list(self): + """LongCatImage must appear before FluxSchnell in the models list.""" + models = comfy.supported_models.models + longcat_idx = next(i for i, m in enumerate(models) if m.__name__ == "LongCatImage") + schnell_idx = next(i for i, m in enumerate(models) if m.__name__ == "FluxSchnell") + assert longcat_idx < schnell_idx, ( + f"LongCatImage (index {longcat_idx}) must come before " + f"FluxSchnell (index {schnell_idx}) in the models list" + ) def test_longcat_comfyui_detected_as_longcat(self): sd = _make_longcat_comfyui_sd() From d7eb2ac8cc5d9de3aa62f80768393cf9a8d4c78e Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Thu, 26 Feb 2026 22:13:24 +0100 Subject: [PATCH 14/18] Reduce memory consumption in model_detection tests. --- tests-unit/comfy_test/model_detection_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests-unit/comfy_test/model_detection_test.py b/tests-unit/comfy_test/model_detection_test.py index fdde8994a56..2551a417b04 100644 --- a/tests-unit/comfy_test/model_detection_test.py +++ b/tests-unit/comfy_test/model_detection_test.py @@ -7,7 +7,7 @@ def _make_longcat_comfyui_sd(): """Minimal ComfyUI-format state dict for pre-converted LongCat-Image weights.""" sd = {} - H = 3072 + H = 32 # Reduce hidden state dimension to reduce memory usage C_IN = 16 C_CTX = 3584 @@ -39,7 +39,7 @@ def _make_longcat_comfyui_sd(): def _make_flux_schnell_comfyui_sd(): """Minimal ComfyUI-format state dict for standard Flux Schnell.""" sd = {} - H = 3072 + H = 32 # Reduce hidden state dimension to reduce memory usage C_IN = 16 sd["img_in.weight"] = torch.empty(H, C_IN * 4) From 8e668d9e67790eaa88897518556cfa75d5061d14 Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Fri, 27 Feb 2026 21:00:42 +0100 Subject: [PATCH 15/18] Update LongCat-Image blueprint. --- blueprints/Text to Image (LongCat-Image).json | 960 +++++++++++++++++- 1 file changed, 959 insertions(+), 1 deletion(-) diff --git a/blueprints/Text to Image (LongCat-Image).json b/blueprints/Text to Image (LongCat-Image).json index eda14894954..e666e69c400 100644 --- a/blueprints/Text to Image (LongCat-Image).json +++ b/blueprints/Text to Image (LongCat-Image).json @@ -1 +1,959 @@ -{"id": "a7e3b1c0-4f2d-4e8a-9b1c-longcat00001", "revision": 0, "last_node_id": 20, "last_link_id": 20, "nodes": [{"id": 1, "type": "lc-subgraph-001", "pos": [0, 1230], "size": [400, 470], "flags": {}, "order": 0, "mode": 0, "inputs": [{"label": "prompt", "name": "text", "type": "STRING", "widget": {"name": "text"}, "link": null}, {"name": "width", "type": "INT", "widget": {"name": "width"}, "link": null}, {"name": "height", "type": "INT", "widget": {"name": "height"}, "link": null}, {"name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": null}, {"name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": null}, {"name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": null}], "outputs": [{"localized_name": "IMAGE", "name": "IMAGE", "type": "IMAGE", "links": []}], "properties": {"proxyWidgets": [["-1", "text"], ["-1", "width"], ["-1", "height"], ["7", "seed"], ["7", "control_after_generate"], ["-1", "unet_name"], ["-1", "clip_name"], ["-1", "vae_name"]], "cnr_id": "comfy-core", "ver": "0.3.73", "enableTabs": false}, "widgets_values": ["A young Asian woman wearing a yellow knit sweater with a white necklace, sitting with her hands on her knees and a serene expression. The background is a rough brick wall with warm afternoon sunlight.", 768, 1344, null, null, "longcat_image_bf16.safetensors", "qwen_2.5_vl_7b.safetensors", "ae.safetensors"]}], "links": [], "groups": [], "definitions": {"subgraphs": [{"id": "lc-subgraph-001", "version": 1, "state": {"lastGroupId": 4, "lastNodeId": 20, "lastLinkId": 20, "lastRerouteId": 0}, "revision": 0, "config": {}, "name": "local-Text to Image (LongCat-Image)", "inputNode": {"id": -10, "bounding": [-80, 425, 120, 160]}, "outputNode": {"id": -20, "bounding": [1490, 415, 120, 60]}, "inputs": [{"id": "inp-text", "name": "text", "type": "STRING", "linkIds": [10], "label": "prompt", "pos": [20, 445]}, {"id": "inp-width", "name": "width", "type": "INT", "linkIds": [11], "pos": [20, 465]}, {"id": "inp-height", "name": "height", "type": "INT", "linkIds": [12], "pos": [20, 485]}, {"id": "inp-unet", "name": "unet_name", "type": "COMBO", "linkIds": [13], "pos": [20, 505]}, {"id": "inp-clip", "name": "clip_name", "type": "COMBO", "linkIds": [14], "pos": [20, 525]}, {"id": "inp-vae", "name": "vae_name", "type": "COMBO", "linkIds": [15], "pos": [20, 545]}], "outputs": [{"id": "out-image", "name": "IMAGE", "type": "IMAGE", "linkIds": [9], "localized_name": "IMAGE", "pos": [1510, 435]}], "widgets": [], "nodes": [{"id": 1, "type": "UNETLoader", "pos": [110, 200], "size": [270, 82], "flags": {}, "order": 0, "mode": 0, "inputs": [{"name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": 13}, {"name": "weight_dtype", "type": "COMBO", "widget": {"name": "weight_dtype"}, "link": null}], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [1]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "UNETLoader", "models": [{"name": "longcat_image_bf16.safetensors", "url": "https://huggingface.co/TalmajM/LongCat-Image_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/longcat_image_bf16.safetensors", "directory": "unet"}]}, "widgets_values": ["longcat_image_bf16.safetensors", "default"]}, {"id": 2, "type": "CLIPLoader", "pos": [110, 330], "size": [270, 106], "flags": {}, "order": 1, "mode": 0, "inputs": [{"name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": 14}, {"name": "type", "type": "COMBO", "widget": {"name": "type"}, "link": null}, {"name": "device", "shape": 7, "type": "COMBO", "widget": {"name": "device"}, "link": null}], "outputs": [{"name": "CLIP", "type": "CLIP", "links": [2, 16]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPLoader", "models": [{"name": "qwen_2.5_vl_7b.safetensors", "url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b.safetensors", "directory": "text_encoders"}]}, "widgets_values": ["qwen_2.5_vl_7b.safetensors", "longcat_image", "default"]}, {"id": 3, "type": "VAELoader", "pos": [110, 480], "size": [270, 58], "flags": {}, "order": 2, "mode": 0, "inputs": [{"name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": 15}], "outputs": [{"name": "VAE", "type": "VAE", "links": [3]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "VAELoader", "models": [{"name": "ae.safetensors", "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors", "directory": "vae"}]}, "widgets_values": ["ae.safetensors"]}, {"id": 4, "type": "CLIPTextEncodeLongCatImage", "pos": [430, 200], "size": [410, 250], "flags": {}, "order": 3, "mode": 0, "inputs": [{"name": "clip", "type": "CLIP", "link": 2}, {"name": "text", "type": "STRING", "widget": {"name": "text"}, "link": 10}, {"name": "guidance", "type": "FLOAT", "widget": {"name": "guidance"}, "link": null}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [4]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPTextEncodeLongCatImage"}, "widgets_values": ["", 4.0]}, {"id": 5, "type": "CLIPTextEncodeLongCatImage", "pos": [430, 510], "size": [410, 120], "flags": {}, "order": 4, "mode": 0, "inputs": [{"name": "clip", "type": "CLIP", "link": 16}, {"name": "text", "type": "STRING", "widget": {"name": "text"}, "link": null}, {"name": "guidance", "type": "FLOAT", "widget": {"name": "guidance"}, "link": null}], "outputs": [{"name": "CONDITIONING", "type": "CONDITIONING", "links": [6]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CLIPTextEncodeLongCatImage"}, "widgets_values": ["", 4.0]}, {"id": 10, "type": "CFGRenormLongCatImage", "pos": [880, 160], "size": [280, 26], "flags": {}, "order": 5, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 1}], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [17]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "CFGRenormLongCatImage"}, "widgets_values": []}, {"id": 6, "type": "EmptySD3LatentImage", "pos": [110, 630], "size": [260, 106], "flags": {}, "order": 6, "mode": 0, "inputs": [{"name": "width", "type": "INT", "widget": {"name": "width"}, "link": 11}, {"name": "height", "type": "INT", "widget": {"name": "height"}, "link": 12}, {"name": "batch_size", "type": "INT", "widget": {"name": "batch_size"}, "link": null}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [7]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "EmptySD3LatentImage"}, "widgets_values": [768, 1344, 1]}, {"id": 7, "type": "KSampler", "pos": [880, 230], "size": [315, 262], "flags": {}, "order": 7, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 17}, {"name": "positive", "type": "CONDITIONING", "link": 4}, {"name": "negative", "type": "CONDITIONING", "link": 6}, {"name": "latent_image", "type": "LATENT", "link": 7}, {"name": "seed", "type": "INT", "widget": {"name": "seed"}, "link": null}, {"name": "steps", "type": "INT", "widget": {"name": "steps"}, "link": null}, {"name": "cfg", "type": "FLOAT", "widget": {"name": "cfg"}, "link": null}, {"name": "sampler_name", "type": "COMBO", "widget": {"name": "sampler_name"}, "link": null}, {"name": "scheduler", "type": "COMBO", "widget": {"name": "scheduler"}, "link": null}, {"name": "denoise", "type": "FLOAT", "widget": {"name": "denoise"}, "link": null}], "outputs": [{"name": "LATENT", "type": "LATENT", "links": [8]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "KSampler"}, "widgets_values": [0, "randomize", 20, 4.0, "euler", "simple", 1.0]}, {"id": 8, "type": "VAEDecode", "pos": [1220, 160], "size": [210, 46], "flags": {}, "order": 8, "mode": 0, "inputs": [{"name": "samples", "type": "LATENT", "link": 8}, {"name": "vae", "type": "VAE", "link": 3}], "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [9]}], "properties": {"cnr_id": "comfy-core", "ver": "0.3.73", "Node name for S&R": "VAEDecode"}, "widgets_values": []}], "groups": [{"id": 1, "title": "Image size", "bounding": [100, 560, 290, 200], "color": "#3f789e", "font_size": 24, "flags": {}}, {"id": 2, "title": "Prompt", "bounding": [410, 130, 450, 540], "color": "#3f789e", "font_size": 24, "flags": {}}, {"id": 3, "title": "Models", "bounding": [100, 130, 290, 413], "color": "#3f789e", "font_size": 24, "flags": {}}], "links": [{"id": 1, "origin_id": 1, "origin_slot": 0, "target_id": 10, "target_slot": 0, "type": "MODEL"}, {"id": 2, "origin_id": 2, "origin_slot": 0, "target_id": 4, "target_slot": 0, "type": "CLIP"}, {"id": 3, "origin_id": 3, "origin_slot": 0, "target_id": 8, "target_slot": 1, "type": "VAE"}, {"id": 4, "origin_id": 4, "origin_slot": 0, "target_id": 7, "target_slot": 1, "type": "CONDITIONING"}, {"id": 6, "origin_id": 5, "origin_slot": 0, "target_id": 7, "target_slot": 2, "type": "CONDITIONING"}, {"id": 7, "origin_id": 6, "origin_slot": 0, "target_id": 7, "target_slot": 3, "type": "LATENT"}, {"id": 8, "origin_id": 7, "origin_slot": 0, "target_id": 8, "target_slot": 0, "type": "LATENT"}, {"id": 9, "origin_id": 8, "origin_slot": 0, "target_id": -20, "target_slot": 0, "type": "IMAGE"}, {"id": 10, "origin_id": -10, "origin_slot": 0, "target_id": 4, "target_slot": 1, "type": "STRING"}, {"id": 11, "origin_id": -10, "origin_slot": 1, "target_id": 6, "target_slot": 0, "type": "INT"}, {"id": 12, "origin_id": -10, "origin_slot": 2, "target_id": 6, "target_slot": 1, "type": "INT"}, {"id": 13, "origin_id": -10, "origin_slot": 3, "target_id": 1, "target_slot": 0, "type": "COMBO"}, {"id": 14, "origin_id": -10, "origin_slot": 4, "target_id": 2, "target_slot": 0, "type": "COMBO"}, {"id": 15, "origin_id": -10, "origin_slot": 5, "target_id": 3, "target_slot": 0, "type": "COMBO"}, {"id": 16, "origin_id": 2, "origin_slot": 0, "target_id": 5, "target_slot": 0, "type": "CLIP"}, {"id": 17, "origin_id": 10, "origin_slot": 0, "target_id": 7, "target_slot": 0, "type": "MODEL"}], "extra": {"workflowRendererVersion": "LG"}, "category": "Image generation and editing/Text to image"}]}, "config": {}, "extra": {"frontendVersion": "1.37.10", "workflowRendererVersion": "LG"}, "version": 0.4} +{ + "id": "a7e3b1c0-4f2d-4e8a-9b1c-longcat00001", + "revision": 0, + "last_node_id": 20, + "last_link_id": 20, + "nodes": [ + { + "id": 1, + "type": "lc-subgraph-001", + "pos": [ + 0, + 1230 + ], + "size": [ + 400, + 470 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "label": "prompt", + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": null + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "text" + ], + [ + "-1", + "width" + ], + [ + "-1", + "height" + ], + [ + "7", + "seed" + ], + [ + "7", + "control_after_generate" + ], + [ + "-1", + "unet_name" + ], + [ + "-1", + "clip_name" + ], + [ + "-1", + "vae_name" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.3.73", + "enableTabs": false + }, + "widgets_values": [ + "A young Asian woman wearing a yellow knit sweater with a white necklace, sitting with her hands on her knees and a serene expression. The background is a rough brick wall with warm afternoon sunlight.", + 768, + 1344, + null, + null, + "longcat_image_bf16.safetensors", + "qwen_2.5_vl_7b.safetensors", + "ae.safetensors" + ] + } + ], + "links": [], + "groups": [], + "definitions": { + "subgraphs": [ + { + "id": "lc-subgraph-001", + "version": 1, + "state": { + "lastGroupId": 4, + "lastNodeId": 20, + "lastLinkId": 20, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "local-Text to Image (LongCat-Image)", + "inputNode": { + "id": -10, + "bounding": [ + -80, + 425, + 120, + 160 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 1565.1697026054353, + 250.66308553326058, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "inp-text", + "name": "text", + "type": "STRING", + "linkIds": [ + 10 + ], + "label": "prompt", + "pos": [ + 20, + 445 + ] + }, + { + "id": "inp-width", + "name": "width", + "type": "INT", + "linkIds": [ + 11 + ], + "pos": [ + 20, + 465 + ] + }, + { + "id": "inp-height", + "name": "height", + "type": "INT", + "linkIds": [ + 12 + ], + "pos": [ + 20, + 485 + ] + }, + { + "id": "inp-unet", + "name": "unet_name", + "type": "COMBO", + "linkIds": [ + 13 + ], + "pos": [ + 20, + 505 + ] + }, + { + "id": "inp-clip", + "name": "clip_name", + "type": "COMBO", + "linkIds": [ + 14 + ], + "pos": [ + 20, + 525 + ] + }, + { + "id": "inp-vae", + "name": "vae_name", + "type": "COMBO", + "linkIds": [ + 15 + ], + "pos": [ + 20, + 545 + ] + } + ], + "outputs": [ + { + "id": "out-image", + "name": "IMAGE", + "type": "IMAGE", + "linkIds": [ + 9 + ], + "localized_name": "IMAGE", + "pos": [ + 1585.1697026054353, + 270.6630855332606 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 4, + "type": "CLIPTextEncode", + "pos": [ + 430, + 200 + ], + "size": [ + 280, + 200 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "localized_name": "clip", + "name": "clip", + "type": "CLIP", + "link": 2 + }, + { + "localized_name": "text", + "name": "text", + "type": "STRING", + "widget": { + "name": "text" + }, + "link": 10 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 18 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.73", + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "A young Asian woman wearing a yellow knit sweater with a white necklace, sitting with her hands on her knees and a serene expression. The background is a rough brick wall with warm afternoon sunlight." + ] + }, + { + "id": 11, + "type": "FluxGuidance", + "pos": [ + 730, + 200 + ], + "size": [ + 210, + 58 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "localized_name": "conditioning", + "name": "conditioning", + "type": "CONDITIONING", + "link": 18 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 4 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.73", + "Node name for S&R": "FluxGuidance" + }, + "widgets_values": [ + 4 + ] + }, + { + "id": 5, + "type": "CLIPTextEncode", + "pos": [ + 430, + 440 + ], + "size": [ + 280, + 120 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "localized_name": "clip", + "name": "clip", + "type": "CLIP", + "link": 16 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 19 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.73", + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ] + }, + { + "id": 12, + "type": "FluxGuidance", + "pos": [ + 730, + 440 + ], + "size": [ + 210, + 58 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "localized_name": "conditioning", + "name": "conditioning", + "type": "CONDITIONING", + "link": 19 + } + ], + "outputs": [ + { + "localized_name": "CONDITIONING", + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 6 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.73", + "Node name for S&R": "FluxGuidance" + }, + "widgets_values": [ + 4 + ] + }, + { + "id": 10, + "type": "CFGNorm", + "pos": [ + 960, + 160 + ], + "size": [ + 280, + 58 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 1 + } + ], + "outputs": [ + { + "localized_name": "patched_model", + "name": "patched_model", + "type": "MODEL", + "links": [ + 17 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.73", + "Node name for S&R": "CFGNorm" + }, + "widgets_values": [ + 1 + ] + }, + { + "id": 7, + "type": "KSampler", + "pos": [ + 960, + 260 + ], + "size": [ + 315, + 262 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "MODEL", + "link": 17 + }, + { + "localized_name": "positive", + "name": "positive", + "type": "CONDITIONING", + "link": 4 + }, + { + "localized_name": "negative", + "name": "negative", + "type": "CONDITIONING", + "link": 6 + }, + { + "localized_name": "latent_image", + "name": "latent_image", + "type": "LATENT", + "link": 7 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 8 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.73", + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 0, + "randomize", + 20, + 4, + "euler", + "simple", + 1 + ] + }, + { + "id": 6, + "type": "EmptySD3LatentImage", + "pos": [ + 111.03394052108696, + 666.1721933495651 + ], + "size": [ + 270, + 106 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "localized_name": "width", + "name": "width", + "type": "INT", + "widget": { + "name": "width" + }, + "link": 11 + }, + { + "localized_name": "height", + "name": "height", + "type": "INT", + "widget": { + "name": "height" + }, + "link": 12 + } + ], + "outputs": [ + { + "localized_name": "LATENT", + "name": "LATENT", + "type": "LATENT", + "links": [ + 7 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.73", + "Node name for S&R": "EmptySD3LatentImage" + }, + "widgets_values": [ + 768, + 1344, + 1 + ] + }, + { + "id": 8, + "type": "VAEDecode", + "pos": [ + 1313.1018215632612, + 276.5115985604351 + ], + "size": [ + 210, + 46 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "localized_name": "samples", + "name": "samples", + "type": "LATENT", + "link": 8 + }, + { + "localized_name": "vae", + "name": "vae", + "type": "VAE", + "link": 3 + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 9 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.73", + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [] + }, + { + "id": 2, + "type": "CLIPLoader", + "pos": [ + 110, + 335.1697026054346 + ], + "size": [ + 270, + 106 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "localized_name": "clip_name", + "name": "clip_name", + "type": "COMBO", + "widget": { + "name": "clip_name" + }, + "link": 14 + } + ], + "outputs": [ + { + "localized_name": "CLIP", + "name": "CLIP", + "type": "CLIP", + "links": [ + 2, + 16 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.73", + "Node name for S&R": "CLIPLoader", + "models": [ + { + "name": "qwen_2.5_vl_7b.safetensors", + "url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b.safetensors", + "directory": "text_encoders" + } + ] + }, + "widgets_values": [ + "qwen_2.5_vl_7b.safetensors", + "longcat_image", + "default" + ] + }, + { + "id": 1, + "type": "UNETLoader", + "pos": [ + 110, + 205.16970260543462 + ], + "size": [ + 270, + 82 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "unet_name", + "name": "unet_name", + "type": "COMBO", + "widget": { + "name": "unet_name" + }, + "link": 13 + } + ], + "outputs": [ + { + "localized_name": "MODEL", + "name": "MODEL", + "type": "MODEL", + "links": [ + 1 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.73", + "Node name for S&R": "UNETLoader", + "models": [ + { + "name": "longcat_image_bf16.safetensors", + "url": "https://huggingface.co/TalmajM/LongCat-Image_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/longcat_image_bf16.safetensors", + "directory": "diffusion_models" + } + ] + }, + "widgets_values": [ + "longcat_image_bf16.safetensors", + "default" + ] + }, + { + "id": 3, + "type": "VAELoader", + "pos": [ + 110, + 485.16970260543457 + ], + "size": [ + 270, + 58 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "localized_name": "vae_name", + "name": "vae_name", + "type": "COMBO", + "widget": { + "name": "vae_name" + }, + "link": 15 + } + ], + "outputs": [ + { + "localized_name": "VAE", + "name": "VAE", + "type": "VAE", + "links": [ + 3 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.73", + "Node name for S&R": "VAELoader", + "models": [ + { + "name": "ae.safetensors", + "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors", + "directory": "vae" + } + ] + }, + "widgets_values": [ + "ae.safetensors" + ] + } + ], + "groups": [ + { + "id": 1, + "title": "Image size", + "bounding": [ + 101.03394052108696, + 596.1721933495651, + 290, + 200 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 2, + "title": "Prompt", + "bounding": [ + 420, + 130, + 524.4751672952173, + 457.9321189578261 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + }, + { + "id": 3, + "title": "Models", + "bounding": [ + 100, + 135.1697026054347, + 290, + 430 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + } + ], + "links": [ + { + "id": 1, + "origin_id": 1, + "origin_slot": 0, + "target_id": 10, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 2, + "origin_id": 2, + "origin_slot": 0, + "target_id": 4, + "target_slot": 0, + "type": "CLIP" + }, + { + "id": 3, + "origin_id": 3, + "origin_slot": 0, + "target_id": 8, + "target_slot": 1, + "type": "VAE" + }, + { + "id": 4, + "origin_id": 11, + "origin_slot": 0, + "target_id": 7, + "target_slot": 1, + "type": "CONDITIONING" + }, + { + "id": 6, + "origin_id": 12, + "origin_slot": 0, + "target_id": 7, + "target_slot": 2, + "type": "CONDITIONING" + }, + { + "id": 7, + "origin_id": 6, + "origin_slot": 0, + "target_id": 7, + "target_slot": 3, + "type": "LATENT" + }, + { + "id": 8, + "origin_id": 7, + "origin_slot": 0, + "target_id": 8, + "target_slot": 0, + "type": "LATENT" + }, + { + "id": 9, + "origin_id": 8, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "IMAGE" + }, + { + "id": 10, + "origin_id": -10, + "origin_slot": 0, + "target_id": 4, + "target_slot": 1, + "type": "STRING" + }, + { + "id": 11, + "origin_id": -10, + "origin_slot": 1, + "target_id": 6, + "target_slot": 0, + "type": "INT" + }, + { + "id": 12, + "origin_id": -10, + "origin_slot": 2, + "target_id": 6, + "target_slot": 1, + "type": "INT" + }, + { + "id": 13, + "origin_id": -10, + "origin_slot": 3, + "target_id": 1, + "target_slot": 0, + "type": "COMBO" + }, + { + "id": 14, + "origin_id": -10, + "origin_slot": 4, + "target_id": 2, + "target_slot": 0, + "type": "COMBO" + }, + { + "id": 15, + "origin_id": -10, + "origin_slot": 5, + "target_id": 3, + "target_slot": 0, + "type": "COMBO" + }, + { + "id": 16, + "origin_id": 2, + "origin_slot": 0, + "target_id": 5, + "target_slot": 0, + "type": "CLIP" + }, + { + "id": 17, + "origin_id": 10, + "origin_slot": 0, + "target_id": 7, + "target_slot": 0, + "type": "MODEL" + }, + { + "id": 18, + "origin_id": 4, + "origin_slot": 0, + "target_id": 11, + "target_slot": 0, + "type": "CONDITIONING" + }, + { + "id": 19, + "origin_id": 5, + "origin_slot": 0, + "target_id": 12, + "target_slot": 0, + "type": "CONDITIONING" + } + ], + "extra": { + "workflowRendererVersion": "LG" + } + } + ] + }, + "config": {}, + "extra": { + "frontendVersion": "1.39.19", + "workflowRendererVersion": "LG", + "ds": { + "scale": 0.9671736232454861, + "offset": [ + 532.7716262567731, + 62.20920285243283 + ] + } + }, + "version": 0.4 +} \ No newline at end of file From fe515cf62fe7f043309f6fa7e80ee5719bf0ce2c Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Fri, 27 Feb 2026 21:01:01 +0100 Subject: [PATCH 16/18] Remove unnecessary module. --- comfy_extras/nodes_longcat_image.py | 109 ---------------------------- 1 file changed, 109 deletions(-) delete mode 100644 comfy_extras/nodes_longcat_image.py diff --git a/comfy_extras/nodes_longcat_image.py b/comfy_extras/nodes_longcat_image.py deleted file mode 100644 index 59402dfa08e..00000000000 --- a/comfy_extras/nodes_longcat_image.py +++ /dev/null @@ -1,109 +0,0 @@ -import logging - -import torch -from typing_extensions import override -from comfy_api.latest import ComfyExtension, io - -logger = logging.getLogger(__name__) - - -class CLIPTextEncodeLongCatImage(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="CLIPTextEncodeLongCatImage", - display_name="CLIP Text Encode (LongCat-Image)", - category="advanced/conditioning/longcat", - description="Text encoding for LongCat-Image with character-level quoted text support. Wrap text in quotes for accurate text rendering.", - inputs=[ - io.Clip.Input("clip"), - io.String.Input("text", multiline=True, dynamic_prompts=True), - io.Float.Input("guidance", default=4.0, min=0.0, max=100.0, step=0.1), - ], - outputs=[ - io.Conditioning.Output(), - ], - ) - - @classmethod - def execute(cls, clip, text, guidance) -> io.NodeOutput: - tokens = clip.tokenize(text) - return io.NodeOutput(clip.encode_from_tokens_scheduled(tokens, add_dict={"guidance": guidance})) - - encode = execute - - -class CFGRenormLongCatImage(io.ComfyNode): - """Per-patch CFG renormalization matching HuggingFace's LongCat-Image pipeline. - - After standard CFG combination, rescales the noise prediction at each 2x2 patch - so its norm doesn't exceed the conditional prediction's norm. - """ - - @classmethod - def define_schema(cls): - return io.Schema( - node_id="CFGRenormLongCatImage", - display_name="CFG Renorm (LongCat-Image)", - category="advanced/model/longcat", - description="Applies per-patch CFG renormalization used by the LongCat-Image pipeline. Connect between the model loader and the sampler.", - inputs=[ - io.Model.Input("model"), - ], - outputs=[ - io.Model.Output(), - ], - ) - - @classmethod - def execute(cls, model) -> io.NodeOutput: - def cfg_renorm_post(args): - denoised = args["denoised"] - cond_denoised = args["cond_denoised"] - x = args["input"] - - B, C, H, W = denoised.shape - ps = 2 - - if H % ps != 0 or W % ps != 0: - logger.warning(f"CFG Renorm: incompatible shape {H}x{W}, skipping renorm") - return denoised - - noise = x - denoised - noise_cond = x - cond_denoised - - noise_packed = noise.reshape(B, C, H // ps, ps, W // ps, ps) \ - .permute(0, 2, 4, 1, 3, 5) \ - .reshape(B, -1, C * ps * ps) - cond_packed = noise_cond.reshape(B, C, H // ps, ps, W // ps, ps) \ - .permute(0, 2, 4, 1, 3, 5) \ - .reshape(B, -1, C * ps * ps) - - noise_norm = torch.norm(noise_packed, dim=-1, keepdim=True) - cond_norm = torch.norm(cond_packed, dim=-1, keepdim=True) - - scale = (cond_norm / (noise_norm + 1e-8)).clamp(min=0.0, max=1.0) - - renormed = (noise_packed * scale) \ - .reshape(B, H // ps, W // ps, C, ps, ps) \ - .permute(0, 3, 1, 4, 2, 5) \ - .reshape(B, C, H, W) - - return x - renormed - - m = model.clone() - m.set_model_sampler_post_cfg_function(cfg_renorm_post, disable_cfg1_optimization=True) - return io.NodeOutput(m) - - -class LongCatImageExtension(ComfyExtension): - @override - async def get_node_list(self) -> list[type[io.ComfyNode]]: - return [ - CLIPTextEncodeLongCatImage, - CFGRenormLongCatImage, - ] - - -async def comfy_entrypoint() -> LongCatImageExtension: - return LongCatImageExtension() From 15fd31c4b7c2b4d11927394319e960fe17984676 Mon Sep 17 00:00:00 2001 From: comfyanonymous <121283862+comfyanonymous@users.noreply.github.com> Date: Fri, 27 Feb 2026 22:49:55 -0500 Subject: [PATCH 17/18] Fix --- nodes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nodes.py b/nodes.py index adb2faa79ae..000c0a19574 100644 --- a/nodes.py +++ b/nodes.py @@ -2429,7 +2429,6 @@ async def init_builtin_extra_nodes(): "nodes_tcfg.py", "nodes_context_windows.py", "nodes_qwen.py", - "nodes_longcat_image.py", "nodes_chroma_radiance.py", "nodes_model_patch.py", "nodes_easycache.py", From 409a3a993d37a1c64be99e10fbc637ae3b0379d3 Mon Sep 17 00:00:00 2001 From: comfyanonymous <121283862+comfyanonymous@users.noreply.github.com> Date: Fri, 27 Feb 2026 22:50:53 -0500 Subject: [PATCH 18/18] Temp remove. --- blueprints/Text to Image (LongCat-Image).json | 959 ------------------ 1 file changed, 959 deletions(-) delete mode 100644 blueprints/Text to Image (LongCat-Image).json diff --git a/blueprints/Text to Image (LongCat-Image).json b/blueprints/Text to Image (LongCat-Image).json deleted file mode 100644 index e666e69c400..00000000000 --- a/blueprints/Text to Image (LongCat-Image).json +++ /dev/null @@ -1,959 +0,0 @@ -{ - "id": "a7e3b1c0-4f2d-4e8a-9b1c-longcat00001", - "revision": 0, - "last_node_id": 20, - "last_link_id": 20, - "nodes": [ - { - "id": 1, - "type": "lc-subgraph-001", - "pos": [ - 0, - 1230 - ], - "size": [ - 400, - 470 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "label": "prompt", - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, - "link": null - } - ], - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [] - } - ], - "properties": { - "proxyWidgets": [ - [ - "-1", - "text" - ], - [ - "-1", - "width" - ], - [ - "-1", - "height" - ], - [ - "7", - "seed" - ], - [ - "7", - "control_after_generate" - ], - [ - "-1", - "unet_name" - ], - [ - "-1", - "clip_name" - ], - [ - "-1", - "vae_name" - ] - ], - "cnr_id": "comfy-core", - "ver": "0.3.73", - "enableTabs": false - }, - "widgets_values": [ - "A young Asian woman wearing a yellow knit sweater with a white necklace, sitting with her hands on her knees and a serene expression. The background is a rough brick wall with warm afternoon sunlight.", - 768, - 1344, - null, - null, - "longcat_image_bf16.safetensors", - "qwen_2.5_vl_7b.safetensors", - "ae.safetensors" - ] - } - ], - "links": [], - "groups": [], - "definitions": { - "subgraphs": [ - { - "id": "lc-subgraph-001", - "version": 1, - "state": { - "lastGroupId": 4, - "lastNodeId": 20, - "lastLinkId": 20, - "lastRerouteId": 0 - }, - "revision": 0, - "config": {}, - "name": "local-Text to Image (LongCat-Image)", - "inputNode": { - "id": -10, - "bounding": [ - -80, - 425, - 120, - 160 - ] - }, - "outputNode": { - "id": -20, - "bounding": [ - 1565.1697026054353, - 250.66308553326058, - 120, - 60 - ] - }, - "inputs": [ - { - "id": "inp-text", - "name": "text", - "type": "STRING", - "linkIds": [ - 10 - ], - "label": "prompt", - "pos": [ - 20, - 445 - ] - }, - { - "id": "inp-width", - "name": "width", - "type": "INT", - "linkIds": [ - 11 - ], - "pos": [ - 20, - 465 - ] - }, - { - "id": "inp-height", - "name": "height", - "type": "INT", - "linkIds": [ - 12 - ], - "pos": [ - 20, - 485 - ] - }, - { - "id": "inp-unet", - "name": "unet_name", - "type": "COMBO", - "linkIds": [ - 13 - ], - "pos": [ - 20, - 505 - ] - }, - { - "id": "inp-clip", - "name": "clip_name", - "type": "COMBO", - "linkIds": [ - 14 - ], - "pos": [ - 20, - 525 - ] - }, - { - "id": "inp-vae", - "name": "vae_name", - "type": "COMBO", - "linkIds": [ - 15 - ], - "pos": [ - 20, - 545 - ] - } - ], - "outputs": [ - { - "id": "out-image", - "name": "IMAGE", - "type": "IMAGE", - "linkIds": [ - 9 - ], - "localized_name": "IMAGE", - "pos": [ - 1585.1697026054353, - 270.6630855332606 - ] - } - ], - "widgets": [], - "nodes": [ - { - "id": 4, - "type": "CLIPTextEncode", - "pos": [ - 430, - 200 - ], - "size": [ - 280, - 200 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [ - { - "localized_name": "clip", - "name": "clip", - "type": "CLIP", - "link": 2 - }, - { - "localized_name": "text", - "name": "text", - "type": "STRING", - "widget": { - "name": "text" - }, - "link": 10 - } - ], - "outputs": [ - { - "localized_name": "CONDITIONING", - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 18 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.73", - "Node name for S&R": "CLIPTextEncode" - }, - "widgets_values": [ - "A young Asian woman wearing a yellow knit sweater with a white necklace, sitting with her hands on her knees and a serene expression. The background is a rough brick wall with warm afternoon sunlight." - ] - }, - { - "id": 11, - "type": "FluxGuidance", - "pos": [ - 730, - 200 - ], - "size": [ - 210, - 58 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "localized_name": "conditioning", - "name": "conditioning", - "type": "CONDITIONING", - "link": 18 - } - ], - "outputs": [ - { - "localized_name": "CONDITIONING", - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 4 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.73", - "Node name for S&R": "FluxGuidance" - }, - "widgets_values": [ - 4 - ] - }, - { - "id": 5, - "type": "CLIPTextEncode", - "pos": [ - 430, - 440 - ], - "size": [ - 280, - 120 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "localized_name": "clip", - "name": "clip", - "type": "CLIP", - "link": 16 - } - ], - "outputs": [ - { - "localized_name": "CONDITIONING", - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 19 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.73", - "Node name for S&R": "CLIPTextEncode" - }, - "widgets_values": [ - "" - ] - }, - { - "id": 12, - "type": "FluxGuidance", - "pos": [ - 730, - 440 - ], - "size": [ - 210, - 58 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "localized_name": "conditioning", - "name": "conditioning", - "type": "CONDITIONING", - "link": 19 - } - ], - "outputs": [ - { - "localized_name": "CONDITIONING", - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 6 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.73", - "Node name for S&R": "FluxGuidance" - }, - "widgets_values": [ - 4 - ] - }, - { - "id": 10, - "type": "CFGNorm", - "pos": [ - 960, - 160 - ], - "size": [ - 280, - 58 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 1 - } - ], - "outputs": [ - { - "localized_name": "patched_model", - "name": "patched_model", - "type": "MODEL", - "links": [ - 17 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.73", - "Node name for S&R": "CFGNorm" - }, - "widgets_values": [ - 1 - ] - }, - { - "id": 7, - "type": "KSampler", - "pos": [ - 960, - 260 - ], - "size": [ - 315, - 262 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "localized_name": "model", - "name": "model", - "type": "MODEL", - "link": 17 - }, - { - "localized_name": "positive", - "name": "positive", - "type": "CONDITIONING", - "link": 4 - }, - { - "localized_name": "negative", - "name": "negative", - "type": "CONDITIONING", - "link": 6 - }, - { - "localized_name": "latent_image", - "name": "latent_image", - "type": "LATENT", - "link": 7 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 8 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.73", - "Node name for S&R": "KSampler" - }, - "widgets_values": [ - 0, - "randomize", - 20, - 4, - "euler", - "simple", - 1 - ] - }, - { - "id": 6, - "type": "EmptySD3LatentImage", - "pos": [ - 111.03394052108696, - 666.1721933495651 - ], - "size": [ - 270, - 106 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "localized_name": "width", - "name": "width", - "type": "INT", - "widget": { - "name": "width" - }, - "link": 11 - }, - { - "localized_name": "height", - "name": "height", - "type": "INT", - "widget": { - "name": "height" - }, - "link": 12 - } - ], - "outputs": [ - { - "localized_name": "LATENT", - "name": "LATENT", - "type": "LATENT", - "links": [ - 7 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.73", - "Node name for S&R": "EmptySD3LatentImage" - }, - "widgets_values": [ - 768, - 1344, - 1 - ] - }, - { - "id": 8, - "type": "VAEDecode", - "pos": [ - 1313.1018215632612, - 276.5115985604351 - ], - "size": [ - 210, - 46 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "localized_name": "samples", - "name": "samples", - "type": "LATENT", - "link": 8 - }, - { - "localized_name": "vae", - "name": "vae", - "type": "VAE", - "link": 3 - } - ], - "outputs": [ - { - "localized_name": "IMAGE", - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 9 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.73", - "Node name for S&R": "VAEDecode" - }, - "widgets_values": [] - }, - { - "id": 2, - "type": "CLIPLoader", - "pos": [ - 110, - 335.1697026054346 - ], - "size": [ - 270, - 106 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "localized_name": "clip_name", - "name": "clip_name", - "type": "COMBO", - "widget": { - "name": "clip_name" - }, - "link": 14 - } - ], - "outputs": [ - { - "localized_name": "CLIP", - "name": "CLIP", - "type": "CLIP", - "links": [ - 2, - 16 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.73", - "Node name for S&R": "CLIPLoader", - "models": [ - { - "name": "qwen_2.5_vl_7b.safetensors", - "url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b.safetensors", - "directory": "text_encoders" - } - ] - }, - "widgets_values": [ - "qwen_2.5_vl_7b.safetensors", - "longcat_image", - "default" - ] - }, - { - "id": 1, - "type": "UNETLoader", - "pos": [ - 110, - 205.16970260543462 - ], - "size": [ - 270, - 82 - ], - "flags": {}, - "order": 0, - "mode": 0, - "inputs": [ - { - "localized_name": "unet_name", - "name": "unet_name", - "type": "COMBO", - "widget": { - "name": "unet_name" - }, - "link": 13 - } - ], - "outputs": [ - { - "localized_name": "MODEL", - "name": "MODEL", - "type": "MODEL", - "links": [ - 1 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.73", - "Node name for S&R": "UNETLoader", - "models": [ - { - "name": "longcat_image_bf16.safetensors", - "url": "https://huggingface.co/TalmajM/LongCat-Image_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/longcat_image_bf16.safetensors", - "directory": "diffusion_models" - } - ] - }, - "widgets_values": [ - "longcat_image_bf16.safetensors", - "default" - ] - }, - { - "id": 3, - "type": "VAELoader", - "pos": [ - 110, - 485.16970260543457 - ], - "size": [ - 270, - 58 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "localized_name": "vae_name", - "name": "vae_name", - "type": "COMBO", - "widget": { - "name": "vae_name" - }, - "link": 15 - } - ], - "outputs": [ - { - "localized_name": "VAE", - "name": "VAE", - "type": "VAE", - "links": [ - 3 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.73", - "Node name for S&R": "VAELoader", - "models": [ - { - "name": "ae.safetensors", - "url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors", - "directory": "vae" - } - ] - }, - "widgets_values": [ - "ae.safetensors" - ] - } - ], - "groups": [ - { - "id": 1, - "title": "Image size", - "bounding": [ - 101.03394052108696, - 596.1721933495651, - 290, - 200 - ], - "color": "#3f789e", - "font_size": 24, - "flags": {} - }, - { - "id": 2, - "title": "Prompt", - "bounding": [ - 420, - 130, - 524.4751672952173, - 457.9321189578261 - ], - "color": "#3f789e", - "font_size": 24, - "flags": {} - }, - { - "id": 3, - "title": "Models", - "bounding": [ - 100, - 135.1697026054347, - 290, - 430 - ], - "color": "#3f789e", - "font_size": 24, - "flags": {} - } - ], - "links": [ - { - "id": 1, - "origin_id": 1, - "origin_slot": 0, - "target_id": 10, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 2, - "origin_id": 2, - "origin_slot": 0, - "target_id": 4, - "target_slot": 0, - "type": "CLIP" - }, - { - "id": 3, - "origin_id": 3, - "origin_slot": 0, - "target_id": 8, - "target_slot": 1, - "type": "VAE" - }, - { - "id": 4, - "origin_id": 11, - "origin_slot": 0, - "target_id": 7, - "target_slot": 1, - "type": "CONDITIONING" - }, - { - "id": 6, - "origin_id": 12, - "origin_slot": 0, - "target_id": 7, - "target_slot": 2, - "type": "CONDITIONING" - }, - { - "id": 7, - "origin_id": 6, - "origin_slot": 0, - "target_id": 7, - "target_slot": 3, - "type": "LATENT" - }, - { - "id": 8, - "origin_id": 7, - "origin_slot": 0, - "target_id": 8, - "target_slot": 0, - "type": "LATENT" - }, - { - "id": 9, - "origin_id": 8, - "origin_slot": 0, - "target_id": -20, - "target_slot": 0, - "type": "IMAGE" - }, - { - "id": 10, - "origin_id": -10, - "origin_slot": 0, - "target_id": 4, - "target_slot": 1, - "type": "STRING" - }, - { - "id": 11, - "origin_id": -10, - "origin_slot": 1, - "target_id": 6, - "target_slot": 0, - "type": "INT" - }, - { - "id": 12, - "origin_id": -10, - "origin_slot": 2, - "target_id": 6, - "target_slot": 1, - "type": "INT" - }, - { - "id": 13, - "origin_id": -10, - "origin_slot": 3, - "target_id": 1, - "target_slot": 0, - "type": "COMBO" - }, - { - "id": 14, - "origin_id": -10, - "origin_slot": 4, - "target_id": 2, - "target_slot": 0, - "type": "COMBO" - }, - { - "id": 15, - "origin_id": -10, - "origin_slot": 5, - "target_id": 3, - "target_slot": 0, - "type": "COMBO" - }, - { - "id": 16, - "origin_id": 2, - "origin_slot": 0, - "target_id": 5, - "target_slot": 0, - "type": "CLIP" - }, - { - "id": 17, - "origin_id": 10, - "origin_slot": 0, - "target_id": 7, - "target_slot": 0, - "type": "MODEL" - }, - { - "id": 18, - "origin_id": 4, - "origin_slot": 0, - "target_id": 11, - "target_slot": 0, - "type": "CONDITIONING" - }, - { - "id": 19, - "origin_id": 5, - "origin_slot": 0, - "target_id": 12, - "target_slot": 0, - "type": "CONDITIONING" - } - ], - "extra": { - "workflowRendererVersion": "LG" - } - } - ] - }, - "config": {}, - "extra": { - "frontendVersion": "1.39.19", - "workflowRendererVersion": "LG", - "ds": { - "scale": 0.9671736232454861, - "offset": [ - 532.7716262567731, - 62.20920285243283 - ] - } - }, - "version": 0.4 -} \ No newline at end of file