mirror of
https://github.com/vladmandic/automatic
synced 2026-09-18 16:54:33 +02:00
Submodule extensions-builtin/sdnext-modernui updated: e8fa7a839d...6871752dfd
+4
-4
@@ -248,14 +248,14 @@ timer.startup.record("cv2")
|
||||
|
||||
class _tqdm_cls:
|
||||
def __call__(self, *args, **kwargs):
|
||||
bar_format = 'Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + '{desc}' + '\x1b[0m'
|
||||
return tqdm_lib.tqdm(*args, bar_format=bar_format, ncols=80, colour='#327fba', **kwargs)
|
||||
bar_format = 'Progress {rate_fmt}{postfix} {bar:15} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + '{desc}' + '\x1b[0m'
|
||||
return tqdm_lib.tqdm(*args, bar_format=bar_format, ncols=120, colour='#327fba', **kwargs)
|
||||
|
||||
class _tqdm_old(tqdm_lib.tqdm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.pop("name", None)
|
||||
kwargs['bar_format'] = 'Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + '{desc}' + '\x1b[0m'
|
||||
kwargs['ncols'] = 80
|
||||
kwargs['bar_format'] = 'Progress {rate_fmt}{postfix} {bar:15} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + '{desc}' + '\x1b[0m'
|
||||
kwargs['ncols'] = 120
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
try:
|
||||
|
||||
+1
-1
@@ -291,7 +291,7 @@ def setup_logging(debug=None, trace=None, filename=None):
|
||||
if os.environ.get('SD_TRANSFORMERS_DEBUG', None) is not None:
|
||||
logging.getLogger("transformers").setLevel(logging.DEBUG)
|
||||
else:
|
||||
logging.getLogger("transformers").setLevel(logging.WARNING)
|
||||
logging.getLogger("transformers").setLevel(logging.ERROR)
|
||||
if os.environ.get('SD_TORCH_DEBUG', None) is not None:
|
||||
logging.getLogger("torch").setLevel(logging.DEBUG)
|
||||
else:
|
||||
|
||||
@@ -5,7 +5,88 @@ from modules import shared, errors, devices
|
||||
from modules.logger import log
|
||||
|
||||
|
||||
MIN_LATENT_FRAMES = 7 # decoder floor: fewer latent frames leave the chunked decode with nothing to emit
|
||||
class InterruptLogFilter(logging.Filter):
|
||||
"""Drops the per-block error dumps the modular runner logs when an interrupt raises through it."""
|
||||
def filter(self, record):
|
||||
return 'Interrupted...' not in record.msg
|
||||
|
||||
|
||||
def apply_progress_bar_config(block):
|
||||
kwargs = {
|
||||
"ncols": 120,
|
||||
"colour": "#327fba",
|
||||
"bar_format": "Progress {rate_fmt}{postfix} {bar:15} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} {desc}",
|
||||
}
|
||||
if hasattr(block, "set_progress_bar_config"):
|
||||
block.set_progress_bar_config(**kwargs)
|
||||
for child in getattr(block, "sub_blocks", {}).values():
|
||||
apply_progress_bar_config(child)
|
||||
|
||||
|
||||
def install_state_hook(pipe):
|
||||
runner_log = logging.getLogger('diffusers.modular_pipelines.modular_pipeline')
|
||||
if not any(isinstance(f, InterruptLogFilter) for f in runner_log.filters):
|
||||
runner_log.addFilter(InterruptLogFilter())
|
||||
|
||||
def set_phase(phase: str, module: torch.nn.Module | None = None):
|
||||
# every stage runs inside one pipeline call, so the forward hooks are the only place the current stage is visible; state.begin clears the label per job
|
||||
if getattr(pipe, 'sdnext_phase', None) != phase:
|
||||
pipe.sdnext_phase = phase
|
||||
jobid = getattr(pipe, 'sdnext_phaseid', None)
|
||||
shared.state.end(jobid)
|
||||
pipe.sdnext_phaseid = shared.state.begin(phase)
|
||||
log.debug(f'Pipeline: phase={phase} cls={pipe.__class__.__name__} module={module.__class__.__name__ if module is not None else None}')
|
||||
|
||||
def _pre_transformer_hook(module, args): # pylint: disable=unused-argument
|
||||
set_phase('Generate', module)
|
||||
if shared.state.sampling_steps == 0 and getattr(pipe, 'num_timesteps', 0) > 0:
|
||||
shared.state.sampling_steps = pipe.num_timesteps
|
||||
if shared.state.paused:
|
||||
log.debug('Sampling paused')
|
||||
while shared.state.paused:
|
||||
if shared.state.interrupted or shared.state.skipped:
|
||||
raise AssertionError('Interrupted...')
|
||||
time.sleep(0.1)
|
||||
shared.state.step()
|
||||
if shared.state.interrupted or shared.state.skipped:
|
||||
raise AssertionError('Interrupted...')
|
||||
|
||||
def _pre_text_encode_hook(module, args): # pylint: disable=unused-argument
|
||||
set_phase('TextEncode', module)
|
||||
if shared.state.interrupted or shared.state.skipped:
|
||||
raise AssertionError('Interrupted...')
|
||||
|
||||
def _pre_vae_decode_hook(module, args): # pylint: disable=unused-argument
|
||||
set_phase('Decode', module)
|
||||
if shared.state.interrupted or shared.state.skipped: # fires per tile, so tiled decodes abort promptly
|
||||
raise AssertionError('Interrupted...')
|
||||
|
||||
def _pre_vae_encode_hook(module, args): # pylint: disable=unused-argument
|
||||
set_phase('Encode', module)
|
||||
if shared.state.interrupted or shared.state.skipped: # fires per tile, so tiled encodes abort promptly
|
||||
raise AssertionError('Interrupted...')
|
||||
|
||||
for name in ('transformer', 'transformer_ref'):
|
||||
module = getattr(pipe, name, None)
|
||||
if module is not None:
|
||||
target = getattr(module, 'model', module) # conditioning calls the inner model directly
|
||||
if isinstance(target, torch.nn.Module) and getattr(target, 'sdnext_state_hook', None) is None:
|
||||
target.sdnext_state_hook = target.register_forward_pre_hook(_pre_transformer_hook)
|
||||
|
||||
for name in ('text_encoder', 'text_encoder_2'):
|
||||
module = getattr(pipe, name, None)
|
||||
if module is not None:
|
||||
target = getattr(module, 'model', module) # conditioning calls the inner model directly
|
||||
if isinstance(target, torch.nn.Module) and getattr(target, 'sdnext_state_hook', None) is None:
|
||||
target.sdnext_state_hook = target.register_forward_pre_hook(_pre_text_encode_hook)
|
||||
|
||||
for name in ('vae', 'audio_vae'):
|
||||
decoder = getattr(getattr(pipe, name, None), 'decoder', None) # decode entry points bypass forward, the inner decoder does not
|
||||
if isinstance(decoder, torch.nn.Module) and getattr(decoder, 'sdnext_state_hook', None) is None:
|
||||
decoder.sdnext_state_hook = decoder.register_forward_pre_hook(_pre_vae_decode_hook)
|
||||
encoder = getattr(getattr(pipe, name, None), 'encoder', None) # decode entry points bypass forward, the inner encoder does not
|
||||
if isinstance(encoder, torch.nn.Module) and getattr(encoder, 'sdnext_state_hook', None) is None:
|
||||
encoder.sdnext_state_hook = encoder.register_forward_pre_hook(_pre_vae_encode_hook)
|
||||
|
||||
|
||||
def is_modular(obj) -> bool:
|
||||
@@ -110,157 +191,16 @@ def load_modular_pipe(repo_cls, repo: str, workflow: str | None = None, revision
|
||||
missing = missing_components(pipe, workflow)
|
||||
pipe.sdnext_missing_components = missing # a caller that can recover a component clears its own entry
|
||||
pipe.sdnext_video_workflow = workflow # the workflow this pipe was loaded for, which is what the reference-workflow guard reads; the executed task is chosen per request
|
||||
if hasattr(pipe, 'min_duration') and hasattr(pipe, 'fps'):
|
||||
pipe.sdnext_supported_min_frames = int(pipe.min_duration * pipe.fps) # fresh pipes report the true floor; still mode gates per instance
|
||||
log.info(f'Load modular: cls={pipe.__class__.__name__} workflow={workflow} components={loaded} empty={empty} time={time.time()-t0:.2f}')
|
||||
if missing:
|
||||
# load_components builds each component in its own try/except and reports a failure as a warning on the
|
||||
# diffusers logger, so the reason is in the log above this line rather than in the exception path
|
||||
log.error(f'Load modular: cls={pipe.__class__.__name__} workflow={workflow} missing={missing} components the workflow requires did not load')
|
||||
|
||||
install_state_hook(pipe)
|
||||
apply_progress_bar_config(pipe._blocks) # pylint: disable=protected-access
|
||||
return pipe
|
||||
except Exception as e:
|
||||
log.error(f'Load modular: repo="{repo}" workflow={workflow} {e}')
|
||||
errors.display(e, 'video')
|
||||
return None
|
||||
|
||||
|
||||
def load_modular(selected, offline_args: dict):
|
||||
return load_modular_pipe(selected.repo_cls, selected.repo, workflow=selected.workflow, revision=selected.repo_revision, offline_args=offline_args, base=selected.base)
|
||||
|
||||
|
||||
def apply_minimax_overrides(p, pipe, still: bool = False, audio: bool = True):
|
||||
"""Per-generation constraints shared by the video tab and the image path: canvas and frame
|
||||
alignment, the bespoke scheduler guard, tiling, and the audio/still toggles."""
|
||||
if still:
|
||||
audio = False # a sub-second soundtrack is pure waste on a kept single frame
|
||||
multiple = pipe.canvas_multiple
|
||||
p.task_args['width'] = multiple * (p.width // multiple)
|
||||
p.task_args['height'] = multiple * (p.height // multiple)
|
||||
set_still(pipe, still)
|
||||
if still:
|
||||
frames = 5 # two latent frames; decode pads to the decoder floor and only the first frame is kept
|
||||
log.info(f'Pipeline: cls={pipe.__class__.__name__} mode=still')
|
||||
else:
|
||||
frames = max(getattr(p, 'frames', 1), getattr(pipe, 'sdnext_supported_min_frames', 120))
|
||||
while frames % pipe.vae_frames_per_chunk != pipe.vae_latents_per_chunk: # frame counts align to 17n+5
|
||||
frames += 1
|
||||
max_frames = int(pipe.max_duration * pipe.fps)
|
||||
while frames > max_frames:
|
||||
frames -= pipe.vae_frames_per_chunk
|
||||
if frames != getattr(p, 'frames', None):
|
||||
log.debug(f'Pipeline: cls={pipe.__class__.__name__} frames={getattr(p, "frames", None)} aligned={frames}')
|
||||
p.frames = frames
|
||||
p.task_args['num_frames'] = frames
|
||||
p.steps = max(2, p.steps)
|
||||
p.task_args['num_inference_steps'] = p.steps
|
||||
pipe.num_timesteps = p.steps - 1 # sigma grid includes the terminal point; feeds the progress total
|
||||
if p.sampler_name not in ('None', 'Default'):
|
||||
log.warning(f'Pipeline: cls={pipe.__class__.__name__} sampler={p.sampler_name} unsupported: using model default')
|
||||
p.sampler_name = 'Default' # the model default is the bespoke scheduler pair, which discrete samplers must not replace
|
||||
pipe.vae.enable_tiling() # model always tiles; the shared vae params path may have disabled it
|
||||
set_audio(pipe, audio)
|
||||
p.task_args['output'] = ['videos', 'audio', 'sampling_rate'] if audio else ['videos']
|
||||
p.task_args['output_type'] = 'pil' # the image path otherwise requests latent output, which the decode block rejects
|
||||
p.video_still = still
|
||||
|
||||
|
||||
def set_still(pipe, enabled: bool = True):
|
||||
"""Toggle sub-floor generation for single-frame output. The duration floor is lifted only
|
||||
while the instance flag is set, so other pipes of the class and later normal runs keep the
|
||||
supported floor; decoded latents below the decoder floor are padded by duplicating the
|
||||
trailing latent. The causal VAE keeps padding out of frame 0."""
|
||||
cls = type(pipe)
|
||||
if getattr(cls, 'sdnext_min_duration_orig', None) is None:
|
||||
orig = cls.min_duration
|
||||
cls.sdnext_min_duration_orig = orig
|
||||
cls.min_duration = property(lambda self: 0.0 if getattr(self, 'sdnext_still_mode', False) else orig.fget(self))
|
||||
pipe.sdnext_still_mode = enabled
|
||||
if not enabled:
|
||||
return
|
||||
vae = getattr(pipe, 'vae', None)
|
||||
if vae is not None and getattr(vae, 'sdnext_orig_decode', None) is None:
|
||||
vae.sdnext_orig_decode = vae.decode
|
||||
def padded_decode(z, *args, **kwargs):
|
||||
if z.ndim == 5 and z.shape[2] < MIN_LATENT_FRAMES:
|
||||
pad = z[:, :, -1:].repeat(1, 1, MIN_LATENT_FRAMES - z.shape[2], 1, 1)
|
||||
z = torch.cat([z, pad], dim=2)
|
||||
return vae.sdnext_orig_decode(z, *args, **kwargs)
|
||||
vae.decode = padded_decode
|
||||
|
||||
|
||||
def set_audio(pipe, enabled: bool):
|
||||
"""Pop or restore the audio decode block. The joint denoise still carries the audio rows
|
||||
(a few percent of the sequence), but without the block the audio VAE never runs.
|
||||
Operates on the backing block tree: the public blocks property deep-copies per access."""
|
||||
blocks = getattr(pipe, '_blocks', None) # pylint: disable=protected-access
|
||||
decode = blocks.sub_blocks.get('decode', None) if blocks is not None and hasattr(blocks, 'sub_blocks') else None
|
||||
sub = getattr(decode, 'sub_blocks', None)
|
||||
if sub is None:
|
||||
return
|
||||
if enabled and 'audio' not in sub:
|
||||
stashed = getattr(pipe, 'sdnext_audio_decode_block', None)
|
||||
if stashed is not None:
|
||||
sub.insert('audio', stashed, len(sub))
|
||||
log.debug(f'Pipeline: cls={pipe.__class__.__name__} audio=enabled')
|
||||
elif not enabled and 'audio' in sub:
|
||||
pipe.sdnext_audio_decode_block = sub.pop('audio')
|
||||
log.debug(f'Pipeline: cls={pipe.__class__.__name__} audio=disabled')
|
||||
|
||||
|
||||
class InterruptLogFilter(logging.Filter):
|
||||
"""Drops the per-block error dumps the modular runner logs when an interrupt raises through it."""
|
||||
def filter(self, record):
|
||||
return 'Interrupted...' not in record.getMessage()
|
||||
|
||||
|
||||
def install_state_hook(pipe):
|
||||
runner_log = logging.getLogger('diffusers.modular_pipelines.modular_pipeline')
|
||||
if not any(isinstance(f, InterruptLogFilter) for f in runner_log.filters):
|
||||
runner_log.addFilter(InterruptLogFilter())
|
||||
|
||||
def set_phase(phase: str):
|
||||
# every stage runs inside one pipeline call, so the forward hooks are the only
|
||||
# place the current stage is visible; state.begin clears the label per job
|
||||
if getattr(pipe, 'sdnext_phase', None) != phase:
|
||||
pipe.sdnext_phase = phase
|
||||
shared.state.textinfo = phase
|
||||
log.debug(f'Pipeline: cls={pipe.__class__.__name__} phase={phase}')
|
||||
|
||||
def state_hook(module, args): # pylint: disable=unused-argument
|
||||
set_phase('Generate')
|
||||
if shared.state.sampling_steps == 0 and getattr(pipe, 'num_timesteps', 0) > 0:
|
||||
shared.state.sampling_steps = pipe.num_timesteps
|
||||
if shared.state.paused:
|
||||
log.debug('Sampling paused')
|
||||
while shared.state.paused:
|
||||
if shared.state.interrupted or shared.state.skipped:
|
||||
raise AssertionError('Interrupted...')
|
||||
time.sleep(0.1)
|
||||
shared.state.step()
|
||||
if shared.state.interrupted or shared.state.skipped:
|
||||
raise AssertionError('Interrupted...')
|
||||
|
||||
def encode_hook(module, args): # pylint: disable=unused-argument
|
||||
set_phase('TextEncode')
|
||||
if shared.state.interrupted or shared.state.skipped:
|
||||
raise AssertionError('Interrupted...')
|
||||
|
||||
def decode_hook(module, args): # pylint: disable=unused-argument
|
||||
set_phase('Decode')
|
||||
if shared.state.interrupted or shared.state.skipped: # fires per tile, so tiled decodes abort promptly
|
||||
raise AssertionError('Interrupted...')
|
||||
|
||||
for name in ('transformer', 'transformer_ref'):
|
||||
module = getattr(pipe, name, None)
|
||||
if module is None or getattr(module, 'sdnext_state_hook', None) is not None:
|
||||
continue
|
||||
module.sdnext_state_hook = module.register_forward_pre_hook(state_hook)
|
||||
text_encoder = getattr(pipe, 'text_encoder', None)
|
||||
if text_encoder is not None:
|
||||
target = getattr(text_encoder, 'model', text_encoder) # conditioning calls the inner model directly
|
||||
if isinstance(target, torch.nn.Module) and getattr(target, 'sdnext_state_hook', None) is None:
|
||||
target.sdnext_state_hook = target.register_forward_pre_hook(encode_hook)
|
||||
for name in ('vae', 'audio_vae'):
|
||||
decoder = getattr(getattr(pipe, name, None), 'decoder', None) # decode entry points bypass forward, the inner decoder does not
|
||||
if isinstance(decoder, torch.nn.Module) and getattr(decoder, 'sdnext_state_hook', None) is None:
|
||||
decoder.sdnext_state_hook = decoder.register_forward_pre_hook(decode_hook)
|
||||
@@ -33,7 +33,7 @@ class UpscalerDiffusion(Upscaler):
|
||||
else:
|
||||
model = diffusers.DiffusionPipeline.from_pretrained(scaler.data_path, cache_dir=shared.opts.diffusers_dir, torch_dtype=devices.dtype)
|
||||
if hasattr(model, "set_progress_bar_config"):
|
||||
model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + 'Upscale', ncols=80, colour='#327fba')
|
||||
model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar:15} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + 'Upscale', ncols=120, colour='#327fba')
|
||||
set_diffuser_options(model, vae=None, op='upscaler')
|
||||
self.models[path] = model
|
||||
return self.models[path]
|
||||
|
||||
@@ -428,7 +428,8 @@ def print_stats():
|
||||
|
||||
from modules.sd_models_compile import update_compile_times
|
||||
update_compile_times()
|
||||
dynamo_dct = timer.dynamo.dct(min_time=2.0, no_total=True)
|
||||
dynamo_dct = timer.dynamo.dct(min_time=0.5, no_total=True)
|
||||
timer.dynamo.reset()
|
||||
if dynamo_dct:
|
||||
log.debug(f'Processed: dynamo={dynamo_dct}')
|
||||
|
||||
|
||||
@@ -183,8 +183,21 @@ def get_defaults(model, kwargs):
|
||||
remove = ['return_dict', 'output_type', 'num_images_per_prompt', 'callback', 'callback_on_step_end_tensor_inputs']
|
||||
default_cfg = 0
|
||||
try:
|
||||
signature = inspect.signature(type(model).__call__, follow_wrapped=True)
|
||||
defaults = {k: v.default for k, v in signature.parameters.items() if v.default is not inspect.Parameter.empty and v.default is not None} # get all defaults
|
||||
defaults = {}
|
||||
if hasattr(model, 'blocks') and hasattr(model.blocks, 'inputs'):
|
||||
for input_param in model.blocks.inputs:
|
||||
if input_param.name is None:
|
||||
continue
|
||||
if input_param.default is None:
|
||||
continue
|
||||
if input_param.name in kwargs or input_param.name in remove:
|
||||
continue
|
||||
defaults[input_param.name] = input_param.default
|
||||
|
||||
if not defaults:
|
||||
signature = inspect.signature(type(model).__call__, follow_wrapped=True)
|
||||
defaults = {k: v.default for k, v in signature.parameters.items() if v.default is not inspect.Parameter.empty and v.default is not None} # get all defaults
|
||||
|
||||
defaults = {k: v for k, v in defaults.items() if k not in kwargs} # only log defaults that are not already set by kwargs
|
||||
defaults = {k: v for k, v in defaults.items() if k not in remove} # remove common args that are not useful to log
|
||||
log.debug(f'Pipeline: cls={model.__class__.__name__} defaults={defaults}')
|
||||
@@ -222,9 +235,9 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:l
|
||||
model.register_to_config(boundary_ratio=boundary_target)
|
||||
if hasattr(model, "set_progress_bar_config"):
|
||||
if disable_pbar:
|
||||
model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + desc, ncols=80, colour='#327fba', disable=disable_pbar)
|
||||
model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar:15} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + desc, ncols=120, colour='#327fba', disable=disable_pbar)
|
||||
else:
|
||||
model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + desc, ncols=80, colour='#327fba')
|
||||
model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar:15} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + desc, ncols=120, colour='#327fba')
|
||||
|
||||
possible = get_params(model)
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ def process_pre(p: processing.StableDiffusionProcessing):
|
||||
log.info('Processing modifiers: apply')
|
||||
try:
|
||||
# apply-with-unapply
|
||||
# sd_hijack_compile.install()
|
||||
sd_models_compile.check_deepcache(enable=True)
|
||||
ipadapter.apply(shared.sd_model, p)
|
||||
token_merge.apply_token_merging(shared.sd_model)
|
||||
@@ -235,6 +236,9 @@ def process_base(p: processing.StableDiffusionProcessing):
|
||||
if hasattr(shared.sd_model, 'postprocess') and callable(shared.sd_model.postprocess):
|
||||
output = shared.sd_model.postprocess(p, output)
|
||||
|
||||
if hasattr(shared.sd_model, 'sdnext_phaseid'):
|
||||
shared.state.end(shared.sd_model.sdnext_phaseid)
|
||||
shared.sd_model.sdnext_phaseid = None
|
||||
shared.state.end(jobid)
|
||||
shared.state.nextjob()
|
||||
return output
|
||||
@@ -530,8 +534,8 @@ def update_pipeline(sd_model, p: processing.StableDiffusionProcessing):
|
||||
updated_model = sd_model
|
||||
if 'MiniMaxH3' in sd_model.__class__.__name__ and not isinstance(p, processing.StableDiffusionProcessingVideo):
|
||||
# image tabs run the model in still mode; the video tab applies its own overrides
|
||||
from modules.video_models import video_modular
|
||||
video_modular.apply_minimax_overrides(p, sd_model, still=True, audio=False)
|
||||
from modules.video_models import video_minimax
|
||||
video_minimax.apply_overrides(p, sd_model, still=True, audio=False)
|
||||
if getattr(p, 'detailer_enabled', False):
|
||||
log.warning(f'Processing: cls={sd_model.__class__.__name__} detailer not supported')
|
||||
p.detailer_enabled = False
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import time
|
||||
import logging
|
||||
from modules.timer import dynamo
|
||||
|
||||
|
||||
fn = None
|
||||
ts = None
|
||||
|
||||
|
||||
class CompilationLogInterceptor(logging.Handler):
|
||||
def emit(self, record):
|
||||
try:
|
||||
global fn, ts # pylint: disable=global-statement
|
||||
if 'torchdynamo start tracing' in record.msg:
|
||||
fn = record.msg.split('torchdynamo start tracing')[-1].strip() # extract first string after 'torchdynamo start tracing' and start timer
|
||||
fn = fn.split(' ')[0] # extract first word after 'torchdynamo start tracing'
|
||||
ts = time.time()
|
||||
if 'run_gc_after_compile' in record.msg:
|
||||
if fn is not None:
|
||||
dynamo.ts(fn, ts) # log the time taken for compilation
|
||||
fn = None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def install():
|
||||
dynamo_logger = logging.getLogger("torch._dynamo")
|
||||
dynamo_logger.setLevel(logging.INFO)
|
||||
if not any(isinstance(h, CompilationLogInterceptor) for h in dynamo_logger.handlers):
|
||||
dynamo_interceptor = CompilationLogInterceptor()
|
||||
dynamo_logger.addHandler(dynamo_interceptor)
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import time
|
||||
import tqdm
|
||||
from modules.logger import log
|
||||
|
||||
|
||||
@@ -54,9 +55,10 @@ def xet_get_hijack(*args, **kwargs):
|
||||
if fn and not fn.endswith(".json"):
|
||||
log.debug(f'Download: type=xet mode="{opts.hf_transfer_mode}" fn="{fn}" size={size}')
|
||||
debug(f'Download start: type=xet args={args} kwargs={kwargs}')
|
||||
# import tqdm # TODO xet_download: hijack progress bar
|
||||
# bar_format = 'Download {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + '{desc}' + '\x1b[0m'
|
||||
# kwargs['_tqdm_bar'] = tqdm.tqdm(*args, bar_format=bar_format, ncols=80, colour='#327fba', **kwargs)
|
||||
|
||||
bar_format = 'Download {percentage:3.0f}% {bar:15} {n:.1f}/{total:.1f}{postfix} {elapsed} {remaining} ' + '\x1b[38;5;71m' + '{desc}' + '\x1b[0m'
|
||||
kwargs['_tqdm_bar'] = tqdm.tqdm(*args, bar_format=bar_format, colour='#327fba', unit='MiB', unit_scale=1/(1024*1024), desc=fn, total=size, ncols=120)
|
||||
|
||||
res = orig_xet_get(*args, **kwargs)
|
||||
debug(f'Download end: type=xet res={res}')
|
||||
state.end(jobid)
|
||||
|
||||
@@ -893,7 +893,7 @@ def set_defaults(sd_model, checkpoint_info: CheckpointInfo):
|
||||
sd_model.logvar = sd_model.logvar.to(devices.device) if hasattr(sd_model, 'logvar') else None # fix for training
|
||||
shared.opts.data["sd_checkpoint_hash"] = checkpoint_info.sha256
|
||||
if hasattr(sd_model, "set_progress_bar_config"):
|
||||
sd_model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining}', ncols=80, colour='#327fba')
|
||||
sd_model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar:15} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining}', ncols=120, colour='#327fba')
|
||||
|
||||
|
||||
def load_diffuser(checkpoint_info: CheckpointInfo | None = None, op='model', revision=None): # pylint: disable=unused-argument
|
||||
|
||||
@@ -391,17 +391,6 @@ def update_compile_times():
|
||||
try:
|
||||
times = [float(t.strip()) for t in parts[1:] if t.strip()]
|
||||
if times:
|
||||
# parsed.append((fn, sum(times), len(times), max(times)))
|
||||
dynamo.add(fn, round(sum(times), 2))
|
||||
except ValueError:
|
||||
continue
|
||||
"""
|
||||
parsed.sort(key=lambda x: x[1], reverse=True)
|
||||
results = {}
|
||||
min_time = 0.1
|
||||
for fn, total, count, max_val in parsed:
|
||||
if total > min_time:
|
||||
dynamo.ts(fn, total)
|
||||
results[fn] = { "total": round(total, 2), "count": count, "avg": round(total / count, 2), "max": round(max_val, 2) }
|
||||
return results
|
||||
"""
|
||||
|
||||
@@ -164,7 +164,7 @@ def apply_group_offload_component(module, module_name: str, main: bool) -> bool:
|
||||
module = accelerate.hooks.remove_hook_from_module(module, recurse=True)
|
||||
remove_group_offload_component(module)
|
||||
module.requires_grad_(False)
|
||||
log.debug(f'Offload: type=group op=apply module={module_name} pin={cfg["use_stream"] and not cfg["low_cpu_mem_usage"]}') # before the apply: pinning large components takes a while and would otherwise run silently
|
||||
log.debug(f'Offload: type=group op=apply type={shared.opts.group_offload_type} module={module_name} pin={cfg["use_stream"] and not cfg["low_cpu_mem_usage"]}') # before the apply: pinning large components takes a while and would otherwise run silently
|
||||
apply_group_offloading(module, onload_device=devices.device, offload_device=devices.cpu, **cfg)
|
||||
module.sdnext_group_offload_sig = sig
|
||||
return True
|
||||
|
||||
@@ -21,12 +21,12 @@ def get_default_modes(cmd_opts, mem_stat):
|
||||
cmd_opts.medvram = True # VAE Tiling and other stuff
|
||||
default_offload_mode = "balanced"
|
||||
default_diffusers_offload_min_gpu_memory = 0
|
||||
default_diffusers_offload_always = ', '.join(['T5EncoderModel', 'UMT5EncoderModel'])
|
||||
default_diffusers_offload_always = ''
|
||||
log.info(f"Device detect: memory={gpu_memory:.1f} default=balanced optimization=medvram")
|
||||
elif gpu_memory >= 24:
|
||||
elif gpu_memory >= 22:
|
||||
default_offload_mode = "balanced"
|
||||
default_diffusers_offload_max_gpu_memory = 0.8
|
||||
default_diffusers_offload_always = ', '.join(['T5EncoderModel', 'UMT5EncoderModel'])
|
||||
default_diffusers_offload_always = ''
|
||||
default_diffusers_offload_never = ', '.join(['CLIPTextModel', 'CLIPTextModelWithProjection', 'AutoencoderKL'])
|
||||
log.info(f"Device detect: memory={gpu_memory:.1f} default=balanced optimization=highvram")
|
||||
else:
|
||||
|
||||
@@ -4,9 +4,9 @@ import copy
|
||||
import time
|
||||
import transformers
|
||||
import diffusers
|
||||
from modules import shared, errors, sd_models, sd_checkpoint, model_quant, devices, sd_hijack_te, sd_hijack_vae
|
||||
from modules import shared, errors, sd_models, sd_checkpoint, model_quant, devices, sd_hijack_te, sd_hijack_vae, modular_load
|
||||
from modules.logger import log
|
||||
from modules.video_models import models_def, video_utils, video_overrides, video_cache, video_modular
|
||||
from modules.video_models import models_def, video_utils, video_overrides, video_cache
|
||||
|
||||
|
||||
def _loader(component):
|
||||
@@ -151,8 +151,9 @@ def load_model(selected: models_def.Model):
|
||||
|
||||
# model
|
||||
try:
|
||||
if selected.workflow is not None or video_modular.is_modular(selected.repo_cls):
|
||||
shared.sd_model = video_modular.load_modular(selected, offline_args)
|
||||
if selected.workflow is not None or modular_load.is_modular(selected.repo_cls):
|
||||
from modules.modular_load import load_modular_pipe
|
||||
return load_modular_pipe(selected.repo_cls, selected.repo, workflow=selected.workflow, revision=selected.repo_revision, offline_args=offline_args, base=selected.base)
|
||||
elif selected.repo_cls is None:
|
||||
shared.sd_model = load_custom(selected.repo)
|
||||
else:
|
||||
@@ -208,12 +209,12 @@ def load_model(selected: models_def.Model):
|
||||
shared.sd_model.vae.enable_tiling()
|
||||
tiling = True
|
||||
if hasattr(shared.sd_model, "set_progress_bar_config"):
|
||||
shared.sd_model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m', ncols=80, colour='#327fba')
|
||||
shared.sd_model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar:15} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m', ncols=120, colour='#327fba')
|
||||
|
||||
shared.sd_model = model_quant.do_post_load_quant(shared.sd_model, allow=False)
|
||||
sd_models.set_diffuser_offload(shared.sd_model)
|
||||
if video_modular.is_modular(shared.sd_model):
|
||||
video_modular.install_state_hook(shared.sd_model)
|
||||
if modular_load.is_modular(shared.sd_model):
|
||||
modular_load.install_state_hook(shared.sd_model)
|
||||
|
||||
loaded_model = selected.name
|
||||
msg = f'Load video: cls={shared.sd_model.__class__.__name__} model="{selected.name}" time={t1-t0:.2f}'
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import torch
|
||||
from modules.logger import log
|
||||
|
||||
|
||||
MIN_LATENT_FRAMES = 7 # decoder floor: fewer latent frames leave the chunked decode with nothing to emit
|
||||
|
||||
|
||||
def apply_overrides(p, pipe, still: bool = False, audio: bool = True):
|
||||
"""Per-generation constraints shared by the video tab and the image path: canvas and frame
|
||||
alignment, the bespoke scheduler guard, tiling, and the audio/still toggles."""
|
||||
if still:
|
||||
audio = False # a sub-second soundtrack is pure waste on a kept single frame
|
||||
multiple = pipe.canvas_multiple
|
||||
p.task_args['width'] = multiple * (p.width // multiple)
|
||||
p.task_args['height'] = multiple * (p.height // multiple)
|
||||
set_still(pipe, still)
|
||||
if still:
|
||||
frames = 5 # two latent frames; decode pads to the decoder floor and only the first frame is kept
|
||||
log.info(f'Pipeline: cls={pipe.__class__.__name__} mode=still')
|
||||
else:
|
||||
frames = max(getattr(p, 'frames', 1), getattr(pipe, 'sdnext_supported_min_frames', 120))
|
||||
while frames % pipe.vae_frames_per_chunk != pipe.vae_latents_per_chunk: # frame counts align to 17n+5
|
||||
frames += 1
|
||||
max_frames = int(pipe.max_duration * pipe.fps)
|
||||
while frames > max_frames:
|
||||
frames -= pipe.vae_frames_per_chunk
|
||||
if frames != getattr(p, 'frames', None):
|
||||
log.debug(f'Pipeline: cls={pipe.__class__.__name__} frames={getattr(p, "frames", None)} aligned={frames}')
|
||||
p.frames = frames
|
||||
p.task_args['num_frames'] = frames
|
||||
p.steps = max(2, p.steps)
|
||||
p.task_args['num_inference_steps'] = p.steps
|
||||
pipe.num_timesteps = p.steps - 1 # sigma grid includes the terminal point; feeds the progress total
|
||||
if p.sampler_name not in ('None', 'Default'):
|
||||
log.warning(f'Pipeline: cls={pipe.__class__.__name__} sampler={p.sampler_name} unsupported: using model default')
|
||||
p.sampler_name = 'Default' # the model default is the bespoke scheduler pair, which discrete samplers must not replace
|
||||
pipe.vae.enable_tiling() # model always tiles; the shared vae params path may have disabled it
|
||||
set_audio(pipe, audio)
|
||||
p.task_args['output'] = ['videos', 'audio', 'sampling_rate'] if audio else ['videos']
|
||||
p.task_args['output_type'] = 'pil' # the image path otherwise requests latent output, which the decode block rejects
|
||||
p.video_still = still
|
||||
|
||||
|
||||
def set_still(pipe, enabled: bool = True):
|
||||
"""Toggle sub-floor generation for single-frame output. The duration floor is lifted only
|
||||
while the instance flag is set, so other pipes of the class and later normal runs keep the
|
||||
supported floor; decoded latents below the decoder floor are padded by duplicating the
|
||||
trailing latent. The causal VAE keeps padding out of frame 0."""
|
||||
cls = type(pipe)
|
||||
if getattr(cls, 'sdnext_min_duration_orig', None) is None:
|
||||
orig = cls.min_duration
|
||||
cls.sdnext_min_duration_orig = orig
|
||||
cls.min_duration = property(lambda self: 0.0 if getattr(self, 'sdnext_still_mode', False) else orig.fget(self))
|
||||
pipe.sdnext_still_mode = enabled
|
||||
if not enabled:
|
||||
return
|
||||
vae = getattr(pipe, 'vae', None)
|
||||
if vae is not None and getattr(vae, 'sdnext_orig_decode', None) is None:
|
||||
vae.sdnext_orig_decode = vae.decode
|
||||
def padded_decode(z, *args, **kwargs):
|
||||
if z.ndim == 5 and z.shape[2] < MIN_LATENT_FRAMES:
|
||||
pad = z[:, :, -1:].repeat(1, 1, MIN_LATENT_FRAMES - z.shape[2], 1, 1)
|
||||
z = torch.cat([z, pad], dim=2)
|
||||
return vae.sdnext_orig_decode(z, *args, **kwargs)
|
||||
vae.decode = padded_decode
|
||||
|
||||
|
||||
def set_audio(pipe, enabled: bool):
|
||||
"""Pop or restore the audio decode block. The joint denoise still carries the audio rows
|
||||
(a few percent of the sequence), but without the block the audio VAE never runs.
|
||||
Operates on the backing block tree: the public blocks property deep-copies per access."""
|
||||
blocks = getattr(pipe, '_blocks', None) # pylint: disable=protected-access
|
||||
decode = blocks.sub_blocks.get('decode', None) if blocks is not None and hasattr(blocks, 'sub_blocks') else None
|
||||
sub = getattr(decode, 'sub_blocks', None)
|
||||
if sub is None:
|
||||
return
|
||||
if enabled and 'audio' not in sub:
|
||||
stashed = getattr(pipe, 'sdnext_audio_decode_block', None)
|
||||
if stashed is not None:
|
||||
sub.insert('audio', stashed, len(sub))
|
||||
log.debug(f'Pipeline: cls={pipe.__class__.__name__} audio=enabled')
|
||||
elif not enabled and 'audio' in sub:
|
||||
pipe.sdnext_audio_decode_block = sub.pop('audio')
|
||||
log.debug(f'Pipeline: cls={pipe.__class__.__name__} audio=disabled')
|
||||
@@ -4,7 +4,6 @@ import diffusers
|
||||
from modules import shared, processing, devices
|
||||
from modules.logger import log
|
||||
from modules.video_models.models_def import Model
|
||||
from modules.video_models import video_modular
|
||||
|
||||
|
||||
debug = log.trace if os.environ.get('SD_VIDEO_DEBUG', None) is not None else lambda *args, **kwargs: None
|
||||
@@ -107,4 +106,5 @@ def set_overrides(p: processing.StableDiffusionProcessingVideo, selected: Model)
|
||||
shared.sd_model.transformer.set_attention_backend("flex")
|
||||
# MiniMax H3
|
||||
if 'MiniMaxH3' in cls:
|
||||
video_modular.apply_minimax_overrides(p, shared.sd_model, still=getattr(p, 'video_still', False), audio=getattr(p, 'video_audio', True))
|
||||
from modules.video_models import video_minimax
|
||||
video_minimax.apply_overrides(p, shared.sd_model, still=getattr(p, 'video_still', False), audio=getattr(p, 'video_audio', True))
|
||||
|
||||
@@ -2,9 +2,9 @@ import os
|
||||
import copy
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from modules import shared, errors, sd_models, processing, devices, images, ui_common, scripts_manager
|
||||
from modules import shared, errors, sd_models, processing, devices, images, ui_common, scripts_manager, modular_load
|
||||
from modules.logger import log
|
||||
from modules.video_models import models_def, video_utils, video_load, video_vae, video_overrides, video_save, video_modular
|
||||
from modules.video_models import models_def, video_utils, video_load, video_vae, video_overrides, video_save
|
||||
from modules.paths import resolve_output_path
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ def resolve_model(engine: str | None, model: str | None) -> tuple[models_def.Mod
|
||||
raise VideoError(f'no video model loaded: cls={cls} select engine and model or load a video-capable checkpoint first', 400)
|
||||
pipe = shared.sd_model
|
||||
workflow = getattr(pipe, 'sdnext_video_workflow', None)
|
||||
if workflow is None and video_modular.is_modular(pipe):
|
||||
if workflow is None and modular_load.is_modular(pipe):
|
||||
workflow = models_def.workflow_for_class(cls) or 'auto' # modular pipes dispatch on inputs, so any workflow marker selects the modular branch
|
||||
ckpt = getattr(pipe, 'sd_checkpoint_info', None)
|
||||
selected = models_def.Model(
|
||||
|
||||
@@ -11,7 +11,7 @@ from pipelines.generic_shared import shared_te_map
|
||||
debug = os.environ.get('SD_LOAD_DEBUG', None) is not None
|
||||
|
||||
|
||||
def get_shared(cls, repo_id, subfolder=None, variant=None):
|
||||
def get_shared(cls, repo_id, subfolder=None, variant=None, shared_id: str | None = None):
|
||||
args = {}
|
||||
if variant is not None:
|
||||
args['variant'] = variant
|
||||
@@ -22,7 +22,8 @@ def get_shared(cls, repo_id, subfolder=None, variant=None):
|
||||
if isinstance(identifiers, str):
|
||||
identifiers = [identifiers]
|
||||
identifiers = [identifier.lower() for identifier in identifiers if identifier is not None]
|
||||
if item['cls'] == cls and (not identifiers or any(identifier in repo_id.lower() for identifier in identifiers)):
|
||||
shared_id = shared_id or repo_id.lower()
|
||||
if item['cls'] == cls and (not identifiers or any(identifier in shared_id for identifier in identifiers)):
|
||||
if item.get('config_class', None) is not None and item.get('config_path', None) is not None:
|
||||
with open(item['config_path'], encoding='utf8') as f:
|
||||
args['config'] = item['config_class'](**json.load(f))
|
||||
@@ -91,6 +92,7 @@ def load_text_encoder(
|
||||
modules_to_not_convert=None,
|
||||
modules_dtype_dict=None,
|
||||
use_safetensors=True,
|
||||
shared_id: str | None = None,
|
||||
**kwargs):
|
||||
|
||||
if shared.state.interrupted:
|
||||
@@ -146,7 +148,7 @@ def load_text_encoder(
|
||||
# 3. load shared from repo
|
||||
if allow_shared and (text_encoder is None):
|
||||
log.debug(f'Load model: text_encoder="{repo_id}" cls={cls_name.__name__} quant="{quant_type}" loader={get_loader("transformers")}')
|
||||
target_repo, extra_args = get_shared(cls_name, repo_id, subfolder=subfolder, variant=variant)
|
||||
target_repo, extra_args = get_shared(cls_name, repo_id, subfolder=subfolder, variant=variant, shared_id=shared_id)
|
||||
text_encoder = cls_name.from_pretrained(
|
||||
target_repo,
|
||||
cache_dir=shared.opts.hfcache_dir,
|
||||
|
||||
@@ -4,7 +4,8 @@ from modules.logger import log
|
||||
|
||||
|
||||
def load_minimax(checkpoint_info, diffusers_load_config=None): # pylint: disable=unused-argument
|
||||
from modules.video_models import video_modular, video_load
|
||||
from modules.video_models import video_load
|
||||
from modules.modular_load import load_modular_pipe
|
||||
repo_id = sd_models.path_to_repo(checkpoint_info)
|
||||
sd_models.hf_auth_check(checkpoint_info)
|
||||
if repo_id is None or repo_id.lower() == 'none':
|
||||
@@ -14,7 +15,7 @@ def load_minimax(checkpoint_info, diffusers_load_config=None): # pylint: disable
|
||||
log.debug(f'Load model: type=MiniMaxH3 repo="{repo_id}" workflow={workflow} offload={shared.opts.diffusers_offload_mode} dtype={devices.dtype}')
|
||||
|
||||
repo_cls = diffusers.MiniMaxH3ModularPipeline
|
||||
pipe = video_modular.load_modular_pipe(
|
||||
pipe = load_modular_pipe(
|
||||
repo_cls,
|
||||
repo_id,
|
||||
workflow=workflow,
|
||||
@@ -24,13 +25,10 @@ def load_minimax(checkpoint_info, diffusers_load_config=None): # pylint: disable
|
||||
)
|
||||
if pipe is None:
|
||||
return None
|
||||
missing = video_modular.missing_components(pipe, workflow)
|
||||
if missing:
|
||||
# a component that failed to build is unusable, and loading it by another route only defers the failure into generation as corrupt output
|
||||
log.error(f'Load model: type=MiniMaxH3 repo="{repo_id}" workflow={workflow} missing={missing}')
|
||||
return None
|
||||
|
||||
video_modular.install_state_hook(pipe)
|
||||
if hasattr(pipe, 'min_duration') and hasattr(pipe, 'fps'):
|
||||
pipe.sdnext_supported_min_frames = int(pipe.min_duration * pipe.fps) # fresh pipes report the true floor; still mode gates per instance
|
||||
|
||||
video_load.loaded_model = None # image-path load invalidates the video tab's name cache
|
||||
if hasattr(pipe, 'vae') and hasattr(pipe.vae, 'enable_tiling'):
|
||||
pipe.vae.enable_tiling()
|
||||
|
||||
@@ -266,7 +266,7 @@ def download_pretrained_from_url(
|
||||
return download_target
|
||||
|
||||
with urllib.request.urlopen(url) as source, open(download_target, "wb") as output:
|
||||
with tqdm(total=int(source.headers.get("Content-Length")), ncols=80, unit='iB', unit_scale=True) as loop:
|
||||
with tqdm(total=int(source.headers.get("Content-Length")), ncols=120, unit='iB', unit_scale=True) as loop:
|
||||
while True:
|
||||
buffer = source.read(8192)
|
||||
if not buffer:
|
||||
|
||||
@@ -283,7 +283,7 @@ class StableDiffusionXLPuLIDPipeline:
|
||||
debug(f'PulID embedding: cond={id_embedding.shape} uncond={uncond_id_embedding.shape}')
|
||||
return uncond_id_embedding, id_embedding
|
||||
|
||||
def set_progress_bar_config(self, bar_format: str | None = None, ncols: int = 80, colour: str | None = None):
|
||||
def set_progress_bar_config(self, bar_format: str | None = None, ncols: int = 120, colour: str | None = None):
|
||||
import functools
|
||||
from tqdm.auto import trange as trange_orig
|
||||
import pulid_sampling
|
||||
|
||||
Reference in New Issue
Block a user