2025-03-19 16:19:50 -04:00
import torch
from torch import nn
from comfy . ldm . flux . layers import (
DoubleStreamBlock ,
LastLayer ,
MLPEmbedder ,
SingleStreamBlock ,
timestep_embedding ,
)
Implement EasyCache and Invent LazyCache (#9496)
* Attempting a universal implementation of EasyCache, starting with flux as test; I screwed up the math a bit, but when I set it just right it works.
* Fixed math to make threshold work as expected, refactored code to use EasyCacheHolder instead of a dict wrapped by object
* Use sigmas from transformer_options instead of timesteps to be compatible with a greater amount of models, make end_percent work
* Make log statement when not skipping useful, preparing for per-cond caching
* Added DIFFUSION_MODEL wrapper around forward function for wan model
* Add subsampling for heuristic inputs
* Add subsampling to output_prev (output_prev_subsampled now)
* Properly consider conds in EasyCache logic
* Created SuperEasyCache to test what happens if caching and reuse is moved outside the scope of conds, added PREDICT_NOISE wrapper to facilitate this test
* Change max reuse_threshold to 3.0
* Mark EasyCache/SuperEasyCache as experimental (beta)
* Make Lumina2 compatible with EasyCache
* Add EasyCache support for Qwen Image
* Fix missing comma, curse you Cursor
* Add EasyCache support to AceStep
* Add EasyCache support to Chroma
* Added EasyCache support to Cosmos Predict t2i
* Make EasyCache not crash with Cosmos Predict ImagToVideo latents, but does not work well at all
* Add EasyCache support to hidream
* Added EasyCache support to hunyuan video
* Added EasyCache support to hunyuan3d
* Added EasyCache support to LTXV (not very good, but does not crash)
* Implemented EasyCache for aura_flow
* Renamed SuperEasyCache to LazyCache, hardcoded subsample_factor to 8 on nodes
* Eatra logging when verbose is true for EasyCache
2025-08-22 19:41:08 -07:00
import comfy . patcher_extension
2025-03-19 16:19:50 -04:00
class Hunyuan3Dv2 ( nn . Module ) :
def __init__ (
self ,
in_channels = 64 ,
context_in_dim = 1536 ,
hidden_size = 1024 ,
mlp_ratio = 4.0 ,
num_heads = 16 ,
depth = 16 ,
depth_single_blocks = 32 ,
qkv_bias = True ,
guidance_embed = False ,
image_model = None ,
dtype = None ,
device = None ,
operations = None
) :
super ( ) . __init__ ( )
self . dtype = dtype
if hidden_size % num_heads != 0 :
raise ValueError (
f " Hidden size { hidden_size } must be divisible by num_heads { num_heads } "
)
self . max_period = 1000 # While reimplementing the model I noticed that they messed up. This 1000 value was meant to be the time_factor but they set the max_period instead
self . latent_in = operations . Linear ( in_channels , hidden_size , bias = True , dtype = dtype , device = device )
self . time_in = MLPEmbedder ( in_dim = 256 , hidden_dim = hidden_size , dtype = dtype , device = device , operations = operations )
self . guidance_in = (
MLPEmbedder ( in_dim = 256 , hidden_dim = hidden_size , dtype = dtype , device = device , operations = operations ) if guidance_embed else None
)
self . cond_in = operations . Linear ( context_in_dim , hidden_size , dtype = dtype , device = device )
self . double_blocks = nn . ModuleList (
[
DoubleStreamBlock (
hidden_size ,
num_heads ,
mlp_ratio = mlp_ratio ,
qkv_bias = qkv_bias ,
dtype = dtype , device = device , operations = operations
)
for _ in range ( depth )
]
)
self . single_blocks = nn . ModuleList (
[
SingleStreamBlock (
hidden_size ,
num_heads ,
mlp_ratio = mlp_ratio ,
dtype = dtype , device = device , operations = operations
)
for _ in range ( depth_single_blocks )
]
)
self . final_layer = LastLayer ( hidden_size , 1 , in_channels , dtype = dtype , device = device , operations = operations )
def forward ( self , x , timestep , context , guidance = None , transformer_options = { } , * * kwargs ) :
Implement EasyCache and Invent LazyCache (#9496)
* Attempting a universal implementation of EasyCache, starting with flux as test; I screwed up the math a bit, but when I set it just right it works.
* Fixed math to make threshold work as expected, refactored code to use EasyCacheHolder instead of a dict wrapped by object
* Use sigmas from transformer_options instead of timesteps to be compatible with a greater amount of models, make end_percent work
* Make log statement when not skipping useful, preparing for per-cond caching
* Added DIFFUSION_MODEL wrapper around forward function for wan model
* Add subsampling for heuristic inputs
* Add subsampling to output_prev (output_prev_subsampled now)
* Properly consider conds in EasyCache logic
* Created SuperEasyCache to test what happens if caching and reuse is moved outside the scope of conds, added PREDICT_NOISE wrapper to facilitate this test
* Change max reuse_threshold to 3.0
* Mark EasyCache/SuperEasyCache as experimental (beta)
* Make Lumina2 compatible with EasyCache
* Add EasyCache support for Qwen Image
* Fix missing comma, curse you Cursor
* Add EasyCache support to AceStep
* Add EasyCache support to Chroma
* Added EasyCache support to Cosmos Predict t2i
* Make EasyCache not crash with Cosmos Predict ImagToVideo latents, but does not work well at all
* Add EasyCache support to hidream
* Added EasyCache support to hunyuan video
* Added EasyCache support to hunyuan3d
* Added EasyCache support to LTXV (not very good, but does not crash)
* Implemented EasyCache for aura_flow
* Renamed SuperEasyCache to LazyCache, hardcoded subsample_factor to 8 on nodes
* Eatra logging when verbose is true for EasyCache
2025-08-22 19:41:08 -07:00
return comfy . patcher_extension . WrapperExecutor . new_class_executor (
self . _forward ,
self ,
comfy . patcher_extension . get_all_wrappers ( comfy . patcher_extension . WrappersMP . DIFFUSION_MODEL , transformer_options )
) . execute ( x , timestep , context , guidance , transformer_options , * * kwargs )
def _forward ( self , x , timestep , context , guidance = None , transformer_options = { } , * * kwargs ) :
2025-03-19 16:19:50 -04:00
x = x . movedim ( - 1 , - 2 )
timestep = 1.0 - timestep
txt = context
img = self . latent_in ( x )
vec = self . time_in ( timestep_embedding ( timestep , 256 , self . max_period ) . to ( dtype = img . dtype ) )
if self . guidance_in is not None :
if guidance is not None :
vec = vec + self . guidance_in ( timestep_embedding ( guidance , 256 , self . max_period ) . to ( img . dtype ) )
txt = self . cond_in ( txt )
pe = None
attn_mask = None
patches_replace = transformer_options . get ( " patches_replace " , { } )
blocks_replace = patches_replace . get ( " dit " , { } )
for i , block in enumerate ( self . double_blocks ) :
if ( " double_block " , i ) in blocks_replace :
def block_wrap ( args ) :
out = { }
out [ " img " ] , out [ " txt " ] = block ( img = args [ " img " ] ,
txt = args [ " txt " ] ,
vec = args [ " vec " ] ,
pe = args [ " pe " ] ,
2025-09-12 15:07:38 -07:00
attn_mask = args . get ( " attn_mask " ) ,
transformer_options = args [ " transformer_options " ] )
2025-03-19 16:19:50 -04:00
return out
out = blocks_replace [ ( " double_block " , i ) ] ( { " img " : img ,
" txt " : txt ,
" vec " : vec ,
" pe " : pe ,
2025-09-12 15:07:38 -07:00
" attn_mask " : attn_mask ,
" transformer_options " : transformer_options } ,
2025-03-19 16:19:50 -04:00
{ " original_block " : block_wrap } )
txt = out [ " txt " ]
img = out [ " img " ]
else :
img , txt = block ( img = img ,
txt = txt ,
vec = vec ,
pe = pe ,
2025-09-12 15:07:38 -07:00
attn_mask = attn_mask ,
transformer_options = transformer_options )
2025-03-19 16:19:50 -04:00
img = torch . cat ( ( txt , img ) , 1 )
for i , block in enumerate ( self . single_blocks ) :
if ( " single_block " , i ) in blocks_replace :
def block_wrap ( args ) :
out = { }
out [ " img " ] = block ( args [ " img " ] ,
vec = args [ " vec " ] ,
pe = args [ " pe " ] ,
2025-09-12 15:07:38 -07:00
attn_mask = args . get ( " attn_mask " ) ,
transformer_options = args [ " transformer_options " ] )
2025-03-19 16:19:50 -04:00
return out
out = blocks_replace [ ( " single_block " , i ) ] ( { " img " : img ,
" vec " : vec ,
" pe " : pe ,
2025-09-12 15:07:38 -07:00
" attn_mask " : attn_mask ,
" transformer_options " : transformer_options } ,
2025-03-19 16:19:50 -04:00
{ " original_block " : block_wrap } )
img = out [ " img " ]
else :
2025-09-12 15:07:38 -07:00
img = block ( img , vec = vec , pe = pe , attn_mask = attn_mask , transformer_options = transformer_options )
2025-03-19 16:19:50 -04:00
img = img [ : , txt . shape [ 1 ] : , . . . ]
img = self . final_layer ( img , vec )
return img . movedim ( - 2 , - 1 ) * ( - 1.0 )