Overview
ComfyUI supports various model types for image generation, each serving a specific purpose in the pipeline. The model system handles loading, caching, memory management, and multi-model workflows.
Model Types
Diffusion Models
The core model for denoising latents:
class CheckpointLoaderSimple :
@ classmethod
def INPUT_TYPES ( s ):
return {
"required" : {
"ckpt_name" : (folder_paths.get_filename_list( "checkpoints" ), {
"tooltip" : "The name of the checkpoint (model) to load."
})
}
}
RETURN_TYPES = ( "MODEL" , "CLIP" , "VAE" )
FUNCTION = "load_checkpoint"
CATEGORY = "loaders"
def load_checkpoint ( self , ckpt_name ):
ckpt_path = folder_paths.get_full_path_or_raise( "checkpoints" , ckpt_name)
out = comfy.sd.load_checkpoint_guess_config(
ckpt_path,
output_vae = True ,
output_clip = True ,
embedding_directory = folder_paths.get_folder_paths( "embeddings" )
)
return out[: 3 ]
Auto-detection : load_checkpoint_guess_config automatically detects the model architecture (SD 1.5, SDXL, SD3, Flux, etc.) from the checkpoint file.
VAE (Variational Autoencoder)
Encodes images to latent space and decodes latents back to images:
class VAELoader :
@ classmethod
def INPUT_TYPES ( s ):
return { "required" : { "vae_name" : (s.vae_list(s), )}}
RETURN_TYPES = ( "VAE" ,)
FUNCTION = "load_vae"
CATEGORY = "loaders"
def load_vae ( self , vae_name ):
if vae_name == "pixel_space" :
# Special case: no encoding/decoding
sd = { "pixel_space_vae" : torch.tensor( 1.0 )}
elif vae_name in self .image_taes:
# Tiny AutoEncoder for fast preview
sd = self .load_taesd(vae_name)
else :
vae_path = folder_paths.get_full_path_or_raise( "vae" , vae_name)
sd, metadata = comfy.utils.load_torch_file(
vae_path, return_metadata = True
)
vae = comfy.sd.VAE( sd = sd, metadata = metadata)
vae.throw_exception_if_invalid()
return (vae,)
TAESD (Tiny AutoEncoder)
Lightweight VAE alternatives for fast preview:
@ staticmethod
def load_taesd ( name ):
sd = {}
encoder = f " { name } _encoder.safetensors"
decoder = f " { name } _decoder.safetensors"
enc = comfy.utils.load_torch_file(
folder_paths.get_full_path_or_raise( "vae_approx" , encoder)
)
for k in enc:
sd[ f "taesd_encoder. { k } " ] = enc[k]
dec = comfy.utils.load_torch_file(
folder_paths.get_full_path_or_raise( "vae_approx" , decoder)
)
for k in dec:
sd[ f "taesd_decoder. { k } " ] = dec[k]
# Set scale and shift based on model type
if name == "taesd" :
sd[ "vae_scale" ] = torch.tensor( 0.18215 )
sd[ "vae_shift" ] = torch.tensor( 0.0 )
elif name == "taesdxl" :
sd[ "vae_scale" ] = torch.tensor( 0.13025 )
sd[ "vae_shift" ] = torch.tensor( 0.0 )
return sd
CLIP (Text Encoder)
Encodes text prompts into conditioning:
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" , "flux" ], )
}
}
RETURN_TYPES = ( "CLIP" ,)
FUNCTION = "load_clip"
CATEGORY = "advanced/loaders"
def load_clip ( self , clip_name , type = "stable_diffusion" ):
clip_type = getattr (comfy.sd.CLIPType,
type .upper(),
comfy.sd.CLIPType. STABLE_DIFFUSION )
clip_path = folder_paths.get_full_path_or_raise(
"text_encoders" , clip_name
)
clip = comfy.sd.load_clip(
ckpt_paths = [clip_path],
embedding_directory = folder_paths.get_folder_paths( "embeddings" ),
clip_type = clip_type
)
return (clip,)
Dual CLIP Models
Some models require two CLIP encoders:
class DualCLIPLoader :
@ classmethod
def INPUT_TYPES ( s ):
return {
"required" : {
"clip_name1" : (folder_paths.get_filename_list( "text_encoders" ),),
"clip_name2" : (folder_paths.get_filename_list( "text_encoders" ),),
"type" : ([ "sdxl" , "sd3" , "flux" ], )
}
}
RETURN_TYPES = ( "CLIP" ,)
FUNCTION = "load_clip"
def load_clip ( self , clip_name1 , clip_name2 , type ):
clip_path1 = folder_paths.get_full_path_or_raise(
"text_encoders" , clip_name1
)
clip_path2 = folder_paths.get_full_path_or_raise(
"text_encoders" , clip_name2
)
clip = comfy.sd.load_clip(
ckpt_paths = [clip_path1, clip_path2],
clip_type = getattr (comfy.sd.CLIPType, type .upper())
)
return (clip,)
SDXL : Requires CLIP-L and CLIP-G
SD3 : Supports CLIP-L + CLIP-G or CLIP-L + T5 or CLIP-G + T5
Flux : Requires CLIP-L and T5
Model Paths
From folder_paths.py:22-53, ComfyUI organizes models in standard directories:
models_dir = os.path.join(base_path, "models" )
folder_names_and_paths = {
"checkpoints" : ([os.path.join(models_dir, "checkpoints" )],
supported_pt_extensions),
"vae" : ([os.path.join(models_dir, "vae" )],
supported_pt_extensions),
"loras" : ([os.path.join(models_dir, "loras" )],
supported_pt_extensions),
"text_encoders" : ([os.path.join(models_dir, "text_encoders" ),
os.path.join(models_dir, "clip" )],
supported_pt_extensions),
"diffusion_models" : ([os.path.join(models_dir, "unet" ),
os.path.join(models_dir, "diffusion_models" )],
supported_pt_extensions),
"controlnet" : ([os.path.join(models_dir, "controlnet" ),
os.path.join(models_dir, "t2i_adapter" )],
supported_pt_extensions),
"embeddings" : ([os.path.join(models_dir, "embeddings" )],
supported_pt_extensions),
"upscale_models" : ([os.path.join(models_dir, "upscale_models" )],
supported_pt_extensions),
}
Supported File Extensions
supported_pt_extensions = {
'.ckpt' , '.pt' , '.pt2' , '.bin' ,
'.pth' , '.safetensors' , '.pkl' , '.sft'
}
SafeTensors Recommended : Use .safetensors format for security and reliability. Pickle-based formats (.ckpt, .pt, .pkl) can execute arbitrary code.
LoRA Models
Low-Rank Adaptation models modify base models:
class LoraLoader :
def __init__ ( self ):
self .loaded_lora = None
@ classmethod
def INPUT_TYPES ( s ):
return {
"required" : {
"model" : ( "MODEL" , {
"tooltip" : "The diffusion model to apply LoRA to"
}),
"clip" : ( "CLIP" , {
"tooltip" : "The CLIP model to apply LoRA to"
}),
"lora_name" : (folder_paths.get_filename_list( "loras" ),),
"strength_model" : ( "FLOAT" , {
"default" : 1.0 , "min" : - 100.0 , "max" : 100.0 , "step" : 0.01
}),
"strength_clip" : ( "FLOAT" , {
"default" : 1.0 , "min" : - 100.0 , "max" : 100.0 , "step" : 0.01
})
}
}
RETURN_TYPES = ( "MODEL" , "CLIP" )
FUNCTION = "load_lora"
CATEGORY = "loaders"
def load_lora ( self , model , clip , lora_name , strength_model , strength_clip ):
if strength_model == 0 and strength_clip == 0 :
return (model, clip)
lora_path = folder_paths.get_full_path_or_raise( "loras" , lora_name)
# Cache loaded LoRA
if self .loaded_lora is not None :
if self .loaded_lora[ 0 ] == lora_path:
lora = self .loaded_lora[ 1 ]
else :
self .loaded_lora = None
if lora is None :
lora = comfy.utils.load_torch_file(lora_path, safe_load = True )
self .loaded_lora = (lora_path, lora)
model_lora, clip_lora = comfy.sd.load_lora_for_models(
model, clip, lora, strength_model, strength_clip
)
return (model_lora, clip_lora)
LoRA Chaining : Multiple LoRA loaders can be chained together to apply multiple adaptations. Each LoRA modifies the model passed to it.
Model File Discovery
From folder_paths.py:303-343, ComfyUI recursively scans model directories:
def recursive_search ( directory : str , excluded_dir_names = None ):
if not os.path.isdir(directory):
return [], {}
result = []
dirs = {}
for dirpath, subdirs, filenames in os.walk(directory, followlinks = True ):
# Exclude .git and other special directories
subdirs[:] = [d for d in subdirs if d not in excluded_dir_names]
for file_name in filenames:
relative_path = os.path.relpath(
os.path.join(dirpath, file_name),
directory
)
result.append(relative_path)
# Track directory modification times for cache invalidation
for d in subdirs:
path = os.path.join(dirpath, d)
dirs[path] = os.path.getmtime(path)
return result, dirs
def filter_files_extensions ( files , extensions ):
return sorted ([
f for f in files
if os.path.splitext(f)[ - 1 ].lower() in extensions or len (extensions) == 0
])
Model Caching
filename_list_cache = {} # Global cache
def get_filename_list ( folder_name : str ) -> list[ str ]:
folder_name = map_legacy(folder_name)
out = cached_filename_list_(folder_name)
if out is None :
# Cache miss - scan directory
out = get_filename_list_(folder_name)
filename_list_cache[folder_name] = out
return list (out[ 0 ])
def cached_filename_list_ ( folder_name : str ):
if folder_name not in filename_list_cache:
return None
out = filename_list_cache[folder_name]
# Validate cache - check if directories changed
for folder, time_modified in out[ 1 ].items():
if os.path.getmtime(folder) != time_modified:
return None # Cache invalidated
return out
ControlNet Models
Guidance models for spatial control:
class ControlNetLoader :
@ classmethod
def INPUT_TYPES ( s ):
return {
"required" : {
"control_net_name" : (
folder_paths.get_filename_list( "controlnet" ),
)
}
}
RETURN_TYPES = ( "CONTROL_NET" ,)
FUNCTION = "load_controlnet"
CATEGORY = "loaders"
def load_controlnet ( self , control_net_name ):
controlnet_path = folder_paths.get_full_path_or_raise(
"controlnet" , control_net_name
)
controlnet = comfy.controlnet.load_controlnet(controlnet_path)
if controlnet is None :
raise RuntimeError (
"ERROR: controlnet file is invalid and does not "
"contain a valid controlnet model."
)
return (controlnet,)
Applying ControlNet
class ControlNetApplyAdvanced :
@ classmethod
def INPUT_TYPES ( s ):
return {
"required" : {
"positive" : ( "CONDITIONING" ,),
"negative" : ( "CONDITIONING" ,),
"control_net" : ( "CONTROL_NET" ,),
"image" : ( "IMAGE" ,),
"strength" : ( "FLOAT" , { "default" : 1.0 , "min" : 0.0 , "max" : 10.0 }),
"start_percent" : ( "FLOAT" , { "default" : 0.0 , "min" : 0.0 , "max" : 1.0 }),
"end_percent" : ( "FLOAT" , { "default" : 1.0 , "min" : 0.0 , "max" : 1.0 })
}
}
RETURN_TYPES = ( "CONDITIONING" , "CONDITIONING" )
RETURN_NAMES = ( "positive" , "negative" )
FUNCTION = "apply_controlnet"
def apply_controlnet ( self , positive , negative , control_net , image ,
strength , start_percent , end_percent ):
if strength == 0 :
return (positive, negative)
control_hint = image.movedim( - 1 , 1 )
cnets = {}
out = []
for conditioning in [positive, negative]:
c = []
for t in conditioning:
d = t[ 1 ].copy()
prev_cnet = d.get( 'control' , None )
if prev_cnet in cnets:
c_net = cnets[prev_cnet]
else :
c_net = control_net.copy().set_cond_hint(
control_hint, strength,
(start_percent, end_percent)
)
c_net.set_previous_controlnet(prev_cnet)
cnets[prev_cnet] = c_net
d[ 'control' ] = c_net
d[ 'control_apply_to_uncond' ] = False
c.append([t[ 0 ], d])
out.append(c)
return (out[ 0 ], out[ 1 ])
Model Configuration
Model Paths Configure custom model directories in extra_model_paths.yaml: comfyui :
checkpoints : /path/to/checkpoints
vae : /path/to/vae
loras : /path/to/loras
Memory Management Models are automatically loaded/unloaded based on VRAM:
--lowvram: Aggressive offloading
--normalvram: Moderate offloading
--highvram: Keep models in VRAM
Model Precision Control memory usage with precision:
fp32: Full precision
fp16: Half precision
fp8_e4m3fn: 8-bit float
Safe Loading Use safe_load=True to prevent code execution in pickle files
Advanced Model Loading
UNET-Only Loading
For separate component loading:
class UNETLoader :
@ classmethod
def INPUT_TYPES ( s ):
return {
"required" : {
"unet_name" : (folder_paths.get_filename_list( "diffusion_models" ),),
"weight_dtype" : ([ "default" , "fp8_e4m3fn" ,
"fp8_e4m3fn_fast" , "fp8_e5m2" ],)
}
}
RETURN_TYPES = ( "MODEL" ,)
FUNCTION = "load_unet"
def load_unet ( self , unet_name , weight_dtype ):
model_options = {}
if weight_dtype == "fp8_e4m3fn" :
model_options[ "dtype" ] = torch.float8_e4m3fn
elif weight_dtype == "fp8_e4m3fn_fast" :
model_options[ "dtype" ] = torch.float8_e4m3fn
model_options[ "fp8_optimizations" ] = True
unet_path = folder_paths.get_full_path_or_raise(
"diffusion_models" , unet_name
)
model = comfy.sd.load_diffusion_model(
unet_path, model_options = model_options
)
return (model,)
Model Validation
class VAELoader :
def load_vae ( self , vae_name ):
# ... load vae ...
vae = comfy.sd.VAE( sd = sd, metadata = metadata)
vae.throw_exception_if_invalid() # Validate before returning
return (vae,)
Best Practices
Use subdirectories within model folders
Name models descriptively
Keep model versions separate
Document custom model paths
Check model architecture compatibility
Match VAE to checkpoint type
Verify CLIP encoder requirements
Test LoRA with base model
Prefer SafeTensors format
Use safe_load for pickle files
Verify model sources
Scan for malicious code
See Also
Nodes Using models in node workflows
Execution Model loading during execution
Workflows Building workflows with models