161 lines
6.4 KiB
Python
161 lines
6.4 KiB
Python
import torch
|
|
|
|
from . import chroma_cache
|
|
|
|
|
|
class ChromaCache:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"model": ("MODEL", ),
|
|
# "residual_diff_threshold": (
|
|
# "FLOAT",
|
|
# {
|
|
# "default": 0.0,
|
|
# "min": 0.0,
|
|
# "max": 1.0,
|
|
# "step": 0.001,
|
|
# "tooltip": "Controls the tolerance for caching with lower values being more strict. Setting this to 0 disables the FBCache effect.",
|
|
# },
|
|
# ),
|
|
"start": (
|
|
"FLOAT",
|
|
{
|
|
"default": 0.0,
|
|
"step": 0.01,
|
|
"max": 1.0,
|
|
"min": 0.0,
|
|
"tooltip": "When to enable caching as a percentage of sampling progress. At least 0.3 is recommended because early steps are more sensitive.",
|
|
},
|
|
),
|
|
"end": ("FLOAT", {
|
|
"default": 1.0,
|
|
"step": 0.01,
|
|
"max": 1.0,
|
|
"min": 0.0,
|
|
"tooltip": "When to disable caching as a percentage of sampling progress.",
|
|
}),
|
|
"cache_interval": (
|
|
"INT",
|
|
{
|
|
"default": 1,
|
|
"min": 1,
|
|
"tooltip": "Number of consecutive cache hits before the cache is reset. A value of 1 means the cache is reset every other step.",
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("MODEL",)
|
|
FUNCTION = "patch"
|
|
CATEGORY = "Chroma Cache"
|
|
|
|
def patch(
|
|
self,
|
|
model,
|
|
start=0.0,
|
|
end=1.0,
|
|
cache_interval=1,
|
|
):
|
|
chroma_cache.patch_get_output_data()
|
|
|
|
model_sampling = model.get_model_object("model_sampling")
|
|
start_sigma, end_sigma = (float(
|
|
model_sampling.percent_to_sigma(pct)) for pct in (start, end))
|
|
del model_sampling
|
|
|
|
@torch.compiler.disable()
|
|
def validate_use_cache(use_cached):
|
|
nonlocal consecutive_cache_hits
|
|
use_cached = use_cached and end_sigma <= current_timestep <= start_sigma
|
|
use_cached = use_cached and consecutive_cache_hits < cache_interval
|
|
consecutive_cache_hits = consecutive_cache_hits + 1 if use_cached else 0
|
|
return use_cached
|
|
|
|
prev_timestep = None
|
|
prev_input_state = None
|
|
current_timestep = None
|
|
consecutive_cache_hits = 0
|
|
|
|
def reset_cache_state():
|
|
# Resets the cache state and hits/time tracking variables.
|
|
nonlocal prev_input_state, prev_timestep
|
|
prev_input_state = prev_timestep = None
|
|
chroma_cache.set_current_cache_context(
|
|
chroma_cache.create_cache_context())
|
|
|
|
def ensure_cache_state(model_input: torch.Tensor, timestep: float):
|
|
# Validates the current cache state and hits/time tracking variables
|
|
# and triggers a reset if necessary. Also updates current_timestep and
|
|
# maintains the cache context sequence number.
|
|
nonlocal current_timestep
|
|
input_state = (model_input.shape, model_input.dtype, model_input.device)
|
|
cache_context = chroma_cache.get_current_cache_context()
|
|
# We reset when:
|
|
need_reset = (
|
|
# The previous timestep or input state is not set
|
|
prev_timestep is None or
|
|
prev_input_state is None or
|
|
# Or dtype/device have changed
|
|
prev_input_state[1:] != input_state[1:] or
|
|
# Or the input state after the batch dimension has changed
|
|
prev_input_state[0][1:] != input_state[0][1:] or
|
|
# Or there is no cache context (in this case reset is just making a context)
|
|
cache_context is None or
|
|
# Or the current timestep is higher than the previous one
|
|
timestep > prev_timestep
|
|
)
|
|
if need_reset:
|
|
reset_cache_state()
|
|
elif timestep == prev_timestep:
|
|
# When the current timestep is the same as the previous, we assume ComfyUI has split up
|
|
# the model evaluation into multiple chunks. In this case, we increment the sequence number.
|
|
# Note: No need to check if cache_context is None for these branches as need_reset would be True
|
|
# if so.
|
|
cache_context.sequence_num += 1
|
|
elif timestep < prev_timestep:
|
|
# When the timestep is less than the previous one, we can reset the context sequence number
|
|
cache_context.sequence_num = 0
|
|
current_timestep = timestep
|
|
|
|
def update_cache_state(model_input: torch.Tensor, timestep: float):
|
|
# Updates the previous timestep and input state validation variables.
|
|
nonlocal prev_timestep, prev_input_state
|
|
prev_timestep = timestep
|
|
prev_input_state = (model_input.shape, model_input.dtype, model_input.device)
|
|
|
|
model = model.clone()
|
|
diffusion_model = model.get_model_object("diffusion_model")
|
|
|
|
if diffusion_model.__class__.__name__ != "Chroma":
|
|
raise ValueError(
|
|
f"Unsupported model {diffusion_model.__class__.__name__}")
|
|
|
|
create_patch_function = chroma_cache.create_patch_forward_orig
|
|
|
|
patch_forward = create_patch_function(
|
|
diffusion_model,
|
|
validate_can_use_cache_function=validate_use_cache,
|
|
)
|
|
|
|
def model_unet_function_wrapper(model_function, kwargs):
|
|
try:
|
|
input = kwargs["input"]
|
|
timestep = kwargs["timestep"]
|
|
c = kwargs["c"]
|
|
t = timestep[0].item()
|
|
|
|
ensure_cache_state(input, t)
|
|
|
|
with patch_forward():
|
|
result = model_function(input, timestep, **c)
|
|
update_cache_state(input, t)
|
|
return result
|
|
except Exception as exc:
|
|
reset_cache_state()
|
|
raise exc from None
|
|
|
|
model.set_model_unet_function_wrapper(model_unet_function_wrapper)
|
|
return (model,)
|