import torch from enum import Enum import logging from comfy import model_management from comfy.ldm.models.autoencoder import AutoencoderKL, AutoencodingEngine from comfy.ldm.cascade.stage_a import StageA from comfy.ldm.cascade.stage_c_coder import StageC_coder from comfy.ldm.audio.autoencoder import AudioOobleckVAE import yaml from comfy import clip_vision from comfy import gligen from comfy import diffusers_convert from comfy import model_detection from comfy import sd1_clip from comfy import sdxl_clip import comfy.text_encoders.sd2_clip import comfy.text_encoders.sd3_clip import comfy.text_encoders.sa_t5 import comfy.text_encoders.aura_t5 import comfy.text_encoders.hydit import comfy.text_encoders.flux import comfy.model_patcher import comfy.lora import comfy.t2i_adapter.adapter import comfy.supported_models_base import comfy.taesd.taesd import comfy.sd import comfy.utils import comfy.model_base import comfy.model_management import comfy.model_sampling import torch import folder_paths import json import os from comfy.cli_args import args def save_diffusion_model(output_path, model, metadata=None, extra_keys={}): # Ensure the model is loaded on the GPU for saving model_management.load_models_gpu([model], force_patch_weights=True) # Get the state dictionary for saving, excluding CLIP and VAE sd = model.model.state_dict_for_saving() # Add any extra keys to the state dictionary for k in extra_keys: sd[k] = extra_keys[k] # Ensure all tensors are contiguous before saving for k in sd: t = sd[k] if not t.is_contiguous(): sd[k] = t.contiguous() # Save the state dictionary to the specified output path comfy.utils.save_torch_file(sd, output_path, metadata=metadata) def save_model(model, filename_prefix=None, output_dir=None, prompt=None, extra_pnginfo=None): full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, output_dir) prompt_info = "" if prompt is not None: prompt_info = json.dumps(prompt) metadata = {} enable_modelspec = True if isinstance(model.model, comfy.model_base.SDXL): if isinstance(model.model, comfy.model_base.SDXL_instructpix2pix): metadata["modelspec.architecture"] = "stable-diffusion-xl-v1-edit" else: metadata["modelspec.architecture"] = "stable-diffusion-xl-v1-base" elif isinstance(model.model, comfy.model_base.SDXLRefiner): metadata["modelspec.architecture"] = "stable-diffusion-xl-v1-refiner" elif isinstance(model.model, comfy.model_base.SVD_img2vid): metadata["modelspec.architecture"] = "stable-video-diffusion-img2vid-v1" elif isinstance(model.model, comfy.model_base.SD3): metadata["modelspec.architecture"] = "stable-diffusion-v3-medium" #TODO: other SD3 variants else: enable_modelspec = False if enable_modelspec: metadata["modelspec.sai_model_spec"] = "1.0.0" metadata["modelspec.implementation"] = "sgm" metadata["modelspec.title"] = "{} {}".format(filename, counter) #TODO: # "stable-diffusion-v1", "stable-diffusion-v1-inpainting", "stable-diffusion-v2-512", # "stable-diffusion-v2-768-v", "stable-diffusion-v2-unclip-l", "stable-diffusion-v2-unclip-h", # "v2-inpainting" extra_keys = {} model_sampling = model.get_model_object("model_sampling") if isinstance(model_sampling, comfy.model_sampling.ModelSamplingContinuousEDM): if isinstance(model_sampling, comfy.model_sampling.V_PREDICTION): extra_keys["edm_vpred.sigma_max"] = torch.tensor(model_sampling.sigma_max).float() extra_keys["edm_vpred.sigma_min"] = torch.tensor(model_sampling.sigma_min).float() if model.model.model_type == comfy.model_base.ModelType.EPS: metadata["modelspec.predict_key"] = "epsilon" elif model.model.model_type == comfy.model_base.ModelType.V_PREDICTION: metadata["modelspec.predict_key"] = "v" if not args.disable_metadata: metadata["prompt"] = prompt_info if extra_pnginfo is not None: for x in extra_pnginfo: metadata[x] = json.dumps(extra_pnginfo[x]) output_checkpoint = f"{filename}_{counter:05}_.safetensors" output_checkpoint = os.path.join(full_output_folder, output_checkpoint) save_diffusion_model(output_checkpoint, model, metadata=metadata, extra_keys=extra_keys) class UNETSave: NAME = "Save Diffusion Model" def __init__(self): self.output_dir = folder_paths.get_output_directory() @classmethod def INPUT_TYPES(s): return {"required": { "model": ("MODEL",), "filename_prefix": ("STRING", {"default": "checkpoints/ComfyUI"}),}, "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},} RETURN_TYPES = () FUNCTION = "save" OUTPUT_NODE = True CATEGORY = "advanced/model_merging" def save(self, model, filename_prefix, prompt=None, extra_pnginfo=None): save_model(model, filename_prefix=filename_prefix, output_dir=self.output_dir, prompt=prompt, extra_pnginfo=extra_pnginfo) return {}