Files
olivv-cs-ComfyUI-FunPack/modules/sampling/context_windows/__init__.py
T

117 lines
6.0 KiB
Python

"""Context windows: scenes longer than the model can hold at once.
Nothing is ported: ComfyUI's own `comfy.context_windows` owns the whole mechanism. This builds its
handler and hangs it on the model, the way the stock LTXVContextWindows node does. Core picks it up
inside `calc_cond_batch`, which every sampler already goes through.
The capability gate is the important part. The handler has long existed for plain video, but the
packed audio+video latent needs `BaseModel.map_context_window_to_modalities` to unpack the stream,
map each video window onto its audio window and re-slice the guides. Without it, windowing an LTX
latent would cut the packed tensor blindly and quietly wreck the sound and the guides, so that is
refused, in words, rather than tried.
Core's schedule names put the words the other way round from what this once offered
(`standard_uniform`, not `uniform_standard`); the old spellings are still read, from projects that
saved them.
"""
import inspect
from ..._core import control, patching
ID = "context_windows"
TITLE = "Context windows"
MOUNT = "generation.sampling"
STAGE = "sampling"
CATEGORY = "sampling"
STATUS = "experimental"
REQUIRES = ["ltx_av"]
SCHEDULES = ["standard_uniform", "standard_static", "looped_uniform", "batched"]
FUSES = ["pyramid", "relative", "flat", "overlap-linear"]
_ALIASES = {"uniform_standard": "standard_uniform", "static_standard": "standard_static",
"uniform_looped": "looped_uniform"}
_TEMPORAL = 8 # LTX: real frames per latent frame
SETTINGS = {
"enabled": {"type": "bool", "default": False, "label": "Context windows (long scenes)",
"hint": "Sample a long scene in overlapping windows so it fits the model's memory."},
"length": {"type": "int", "default": 145, "min": 9, "max": 2049, "step": 8, "label": "Window length (frames)",
"when": {"enabled": True}},
"overlap": {"type": "int", "default": 40, "min": 0, "max": 512, "step": 8, "label": "Window overlap (frames)",
"when": {"enabled": True}},
"schedule": {"type": "enum", "default": "standard_uniform", "label": "Window schedule",
"options": [{"value": v, "label": v} for v in SCHEDULES], "when": {"enabled": True}},
"fuse": {"type": "enum", "default": "pyramid", "label": "Window blend",
"options": [{"value": v, "label": v} for v in FUSES], "when": {"enabled": True}},
"freenoise": {"type": "bool", "default": True, "label": "FreeNoise blending", "when": {"enabled": True},
"hint": "Shuffle the starting noise so neighbouring windows share it."},
"retain_first": {"type": "bool", "default": False, "label": "Pin anchor in every window",
"when": {"enabled": True}},
}
def _cleanup(patcher, key):
"""A handler left on this model by an earlier install of ours (a clone carries it forward)."""
options = patcher.model_options
if patching._ours(options.get("context_handler"), key):
options.pop("context_handler", None)
def install(patcher, values, key):
_cleanup(patcher, key)
if not values.get("enabled"):
return None
try:
import comfy.context_windows as cw
import comfy.patcher_extension as pe
except ImportError:
raise control.Unavailable("this ComfyUI has no context-window support (needs ComfyUI >= v0.29.0)")
if not hasattr(getattr(patcher, "model", None), "map_context_window_to_modalities"):
raise control.Unavailable("this ComfyUI cannot window an audio+video latent (it lacks "
"map_context_window_to_modalities); update ComfyUI")
schedule = _ALIASES.get(str(values.get("schedule")), str(values.get("schedule")))
try:
sched = cw.get_matching_context_schedule(schedule)
except ValueError:
raise control.Unavailable(f"schedule {schedule!r} is not one this ComfyUI knows")
try:
fuse = cw.get_matching_fuse_method(str(values.get("fuse")))
except ValueError:
raise control.Unavailable(f"blend {values.get('fuse')!r} is not one this ComfyUI knows")
latent_len = max(((int(values["length"]) - 1) // _TEMPORAL) + 1, 1)
overlap = max(int(values["overlap"]) // _TEMPORAL, 0)
clamped = overlap >= latent_len # core's schedules loop forever / return no windows
overlap = min(overlap, latent_len - 1)
retain = "0" if values.get("retain_first") else ""
kwargs = dict(context_schedule=sched, fuse_method=fuse, context_length=latent_len,
context_overlap=overlap, context_stride=1,
closed_loop=False, dim=2, freenoise=bool(values.get("freenoise")),
cond_retain_index_list=retain, latent_retain_index_list=retain,
split_conds_to_windows=False)
accepted = set(inspect.signature(cw.IndexListContextHandler).parameters)
dropped = [k for k in kwargs if k not in accepted] # this core's handler lacks some keywords
for k in dropped:
kwargs.pop(k)
handler = patching.tag(cw.IndexListContextHandler(**kwargs), key)
patcher.model_options["context_handler"] = handler
patcher.add_wrapper_with_key(pe.WrappersMP.PREPARE_SAMPLING, f"{key}.prepare", cw._prepare_sampling_wrapper)
if values.get("freenoise"):
patcher.add_wrapper_with_key(pe.WrappersMP.SAMPLER_SAMPLE, f"{key}.freenoise", cw._sampler_sample_wrapper)
note = f"windows of {values['length']} frames, {values['overlap']} overlap ({schedule}, {values['fuse']})"
if clamped:
note = (f"windows of {values['length']} frames, overlap cut to {overlap * _TEMPORAL} "
f"(it must be shorter than the window) ({schedule}, {values['fuse']})")
if retain and "latent_retain_index_list" in dropped:
note += "; this ComfyUI cannot pin the anchor in the latent, only in the conditioning"
elif retain and "cond_retain_index_list" in dropped:
note += "; this ComfyUI cannot pin the anchor at all"
if values.get("freenoise") and "freenoise" in dropped:
note += "; this ComfyUI has no FreeNoise blending"
return note
PROVIDES = {"modifier": install}