diff --git a/modules/control/run.py b/modules/control/run.py index efa172260..f04b9c57e 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -691,7 +691,6 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg and getattr(p, 'init_images', None) is None \ and getattr(p, 'image', None) is None: if is_generator: - log.debug(f'Control args: {p.task_args}') yield terminate(f'Mode={p.extra_generation_params.get("Control type", None)} input image is none') return terminate(f'Mode={p.extra_generation_params.get("Control type", None)} input image is none') if unit_type == 'lite': diff --git a/modules/processing_correction.py b/modules/processing_correction.py index b929f233d..6fc206063 100644 --- a/modules/processing_correction.py +++ b/modules/processing_correction.py @@ -85,16 +85,16 @@ def correction(p, timestep, latent): latent = soft_clamp_tensor(latent, threshold=p.hdr_threshold, boundary=p.hdr_boundary) p.extra_generation_params["Latent clamp"] = f'{p.hdr_threshold}/{p.hdr_boundary}' if 600 < timestep < 900 and p.hdr_color != 0: - n = getattr(p, '_correction_steps_mid', 1) + n = getattr(p, 'correction_steps_mid', 1) latent[1:] = center_tensor(latent[1:], channel_shift=p.hdr_color / n, full_shift=float(p.hdr_mode)) p.extra_generation_params["Latent color"] = f'{p.hdr_color}' if 600 < timestep < 900 and p.hdr_tint_ratio != 0: - n = getattr(p, '_correction_steps_mid', 1) + n = getattr(p, 'correction_steps_mid', 1) latent = color_adjust(latent, p.hdr_color_picker, p.hdr_tint_ratio / n) p.extra_generation_params["Latent tint"] = f'{p.hdr_tint_ratio}' p.extra_generation_params["Latent tint color"] = p.hdr_color_picker if timestep < 200 and (p.hdr_brightness != 0): - n = getattr(p, '_correction_steps_late', 1) + n = getattr(p, 'correction_steps_late', 1) latent[0:1] = center_tensor(latent[0:1], full_shift=float(p.hdr_mode), offset=p.hdr_brightness / n) p.extra_generation_params["Latent brightness"] = f'{p.hdr_brightness}' if timestep < 350 and p.hdr_sharpen != 0: @@ -179,35 +179,35 @@ def _count_steps_below(pipe, threshold): def correction_callback(p, timestep, kwargs, pipe=None, initial: bool = False): if initial: if not any([p.hdr_clamp, p.hdr_mode, p.hdr_maximize, p.hdr_sharpen, p.hdr_color, p.hdr_brightness, p.hdr_tint_ratio]): - p._correction_skip = True + p.correction_skip = True return kwargs # always skip for detailer passes (already-corrected image, different resolution) if getattr(p, 'recursion', False): - p._correction_skip = True + p.correction_skip = True return kwargs # optionally skip for hires pass if getattr(p, 'is_hr_pass', False) and not getattr(p, 'hdr_apply_hires', True): - p._correction_skip = True + p.correction_skip = True return kwargs - p._correction_skip = False - p._correction_warned = False + p.correction_skip = False + p.correction_warned = False if pipe is not None: - p._correction_steps_mid = _count_steps_in_range(pipe, 600, 900) - p._correction_steps_late = _count_steps_below(pipe, 200) - elif getattr(p, '_correction_skip', False): + p.correction_steps_mid = _count_steps_in_range(pipe, 600, 900) + p.correction_steps_late = _count_steps_below(pipe, 200) + elif getattr(p, 'correction_skip', False): return kwargs latents = kwargs["latents"] if len(latents.shape) <= 3: # packed latent if pipe is None: - if not getattr(p, '_correction_warned', False): + if not getattr(p, 'correction_warned', False): log.warning(f'Latent correction: shape={latents.shape} packed latent but no pipe reference') - p._correction_warned = True + p.correction_warned = True return kwargs unpacked, pack_type = _unpack_latents(latents, pipe, p) if pack_type == 'unknown': - if not getattr(p, '_correction_warned', False): + if not getattr(p, 'correction_warned', False): log.warning(f'Latent correction: shape={latents.shape} unknown packed format') - p._correction_warned = True + p.correction_warned = True return kwargs for i in range(unpacked.shape[0]): unpacked[i] = correction(p, timestep, unpacked[i]) @@ -228,7 +228,7 @@ def correction_callback(p, timestep, kwargs, pipe=None, initial: bool = False): latents = latents.permute(1, 0, 2, 3).unsqueeze(0) kwargs["latents"] = latents else: - if not getattr(p, '_correction_warned', False): + if not getattr(p, 'correction_warned', False): log.warning(f'Latent correction: shape={latents.shape} unknown latent') - p._correction_warned = True + p.correction_warned = True return kwargs diff --git a/modules/sd_samplers_diffusers.py b/modules/sd_samplers_diffusers.py index a23742ea7..fac12eb2e 100644 --- a/modules/sd_samplers_diffusers.py +++ b/modules/sd_samplers_diffusers.py @@ -10,7 +10,7 @@ from modules.sd_samplers_common import SamplerData, flow_models debug = os.environ.get('SD_SAMPLER_DEBUG', None) is not None debug_log = log.trace if debug else lambda *args, **kwargs: None -_scheduler_overrides = {} # set by sd_samplers.create_sampler() before constructor call +scheduler_overrides = {} # set by sd_samplers.create_sampler() before constructor call # Diffusers schedulers try: @@ -345,80 +345,10 @@ samplers_data_diffusers = [ SamplerData('Same as primary', None, [], {}), ] -_sampler_cls = None -_cls_caps = None - - -def _get_sampler_cls(): - """Lazily build sampler name → scheduler class mapping from samplers_data_diffusers.""" - global _sampler_cls # pylint: disable=global-statement - if _sampler_cls is not None: - return _sampler_cls - _sampler_cls = {} - for sd in samplers_data_diffusers: - if sd.constructor is None: - _sampler_cls[sd.name] = None - continue - try: - closure_globals = inspect.getclosurevars(sd.constructor).globals - _sampler_cls[sd.name] = next((v for v in closure_globals.values() if v is not DiffusionSampler and isinstance(v, type)), None) - except Exception: - _sampler_cls[sd.name] = None - return _sampler_cls - - -def _get_cls_caps(): - """Cache per-sampler capabilities (static, computed once). - - Only tracks flow support and is_flow_only — these are the gates - used by get_sampler_compatibility(). - """ - global _cls_caps # pylint: disable=global-statement - if _cls_caps is not None: - return _cls_caps - sampler_cls = _get_sampler_cls() - _cls_caps = {} - for name, cls in sampler_cls.items(): - if cls is None: - _cls_caps[name] = {'flow': True, 'is_flow_only': False} - continue - is_flow_only = 'FlowMatch' in cls.__name__ - if is_flow_only: - flow = True - else: - try: - src = inspect.getsource(cls) - flow = '"flow_prediction"' in src or "'flow_prediction'" in src - except (TypeError, OSError): - flow = False - _cls_caps[name] = {'flow': flow, 'is_flow_only': is_flow_only} - return _cls_caps - - -def get_sampler_compatibility(model=None): - """Return {sampler_name: bool} compatibility for the loaded model. - - Only the flow/non-flow gate is used: flow models require schedulers - that support flow_prediction, non-flow models reject flow-only schedulers. - The sigmas/scale_noise checks are NOT applied here because some schedulers - (e.g. Res4Lyf) compute flow sigmas internally rather than accepting them - as set_timesteps parameters; the runtime validation in DiffusionSampler - handles API-level mismatches with a fallback. - """ - if model is None: - return {} - default = getattr(model, 'default_scheduler', getattr(model, 'scheduler', None)) - if default is None: - return {} - requires_flow = ('FlowMatch' in default.__class__.__name__) or (getattr(default.config, 'prediction_type', None) == 'flow_prediction') - caps = _get_cls_caps() - result = {} - for name, cap in caps.items(): - if requires_flow: - result[name] = cap['flow'] - else: - result[name] = not cap['is_flow_only'] - return result +def get_override(key, default=None): + if key in scheduler_overrides: + return scheduler_overrides[key] + return getattr(shared.opts, key, default) class DiffusionSampler: @@ -452,16 +382,9 @@ class DiffusionSampler: if key in self.config: self.config[key] = value - # finally apply user preferences (with per-request override support) - overrides = _scheduler_overrides.copy() - def _opt(key, default=None): - if key in overrides: - return overrides[key] - return getattr(shared.opts, key, default) - - if _opt('schedulers_prediction_type') != 'default': - self.config['prediction_type'] = _opt('schedulers_prediction_type') - sched_beta = _opt('schedulers_beta_schedule') + if get_override('schedulers_prediction_type') != 'default': + self.config['prediction_type'] = get_override('schedulers_prediction_type') + sched_beta = get_override('schedulers_beta_schedule') if sched_beta != 'default': if sched_beta == 'linear': self.config['beta_schedule'] = 'linear' @@ -472,9 +395,9 @@ class DiffusionSampler: elif sched_beta == 'sigmoid': self.config['beta_schedule'] = 'sigmoid' - timesteps = re.split(',| ', _opt('schedulers_timesteps')) + timesteps = re.split(',| ', get_override('schedulers_timesteps')) timesteps = [int(x) for x in timesteps if x.isdigit()] - sched_sigma = _opt('schedulers_sigma') + sched_sigma = get_override('schedulers_sigma') if len(timesteps) == 0: if 'sigma_schedule' in self.config: self.config['sigma_schedule'] = sched_sigma if sched_sigma != 'default' else None @@ -494,37 +417,37 @@ class DiffusionSampler: pass # timesteps are set using set_timesteps in set_pipeline_args if 'thresholding' in self.config: - self.config['thresholding'] = _opt('schedulers_use_thresholding') + self.config['thresholding'] = get_override('schedulers_use_thresholding') if 'lower_order_final' in self.config: - self.config['lower_order_final'] = _opt('schedulers_use_loworder') - if 'solver_order' in self.config and int(_opt('schedulers_solver_order')) > 0: - self.config['solver_order'] = int(_opt('schedulers_solver_order')) + self.config['lower_order_final'] = get_override('schedulers_use_loworder') + if 'solver_order' in self.config and int(get_override('schedulers_solver_order')) > 0: + self.config['solver_order'] = int(get_override('schedulers_solver_order')) if 'predict_x0' in self.config: - self.config['solver_type'] = _opt('uni_pc_variant') - if 'beta_start' in self.config and _opt('schedulers_beta_start') > 0: - self.config['beta_start'] = _opt('schedulers_beta_start') - if 'beta_end' in self.config and _opt('schedulers_beta_end') > 0: - self.config['beta_end'] = _opt('schedulers_beta_end') - sched_shift = _opt('schedulers_shift') + self.config['solver_type'] = get_override('uni_pc_variant') + if 'beta_start' in self.config and get_override('schedulers_beta_start') > 0: + self.config['beta_start'] = get_override('schedulers_beta_start') + if 'beta_end' in self.config and get_override('schedulers_beta_end') > 0: + self.config['beta_end'] = get_override('schedulers_beta_end') + sched_shift = get_override('schedulers_shift') if 'shift' in self.config: self.config['shift'] = sched_shift if sched_shift > 0 else 3 if 'flow_shift' in self.config: self.config['flow_shift'] = sched_shift if sched_shift > 0 else 3 if 'use_dynamic_shifting' in self.config: - self.config['use_dynamic_shifting'] = True if sched_shift == 0 else _opt('schedulers_dynamic_shift') + self.config['use_dynamic_shifting'] = True if sched_shift == 0 else get_override('schedulers_dynamic_shift') if 'base_shift' in self.config: - self.config['base_shift'] = _opt('schedulers_base_shift') + self.config['base_shift'] = get_override('schedulers_base_shift') if 'max_shift' in self.config: - self.config['max_shift'] = _opt('schedulers_max_shift') + self.config['max_shift'] = get_override('schedulers_max_shift') if 'use_beta_sigmas' in self.config and 'sigma_schedule' in self.config: self.config['use_beta_sigmas'] = 'StableDiffusion3' in model.__class__.__name__ if 'rescale_betas_zero_snr' in self.config: - self.config['rescale_betas_zero_snr'] = _opt('schedulers_rescale_betas') - sched_ts_spacing = _opt('schedulers_timestep_spacing') + self.config['rescale_betas_zero_snr'] = get_override('schedulers_rescale_betas') + sched_ts_spacing = get_override('schedulers_timestep_spacing') if 'timestep_spacing' in self.config and sched_ts_spacing != 'default' and sched_ts_spacing is not None: self.config['timestep_spacing'] = sched_ts_spacing if 'num_train_timesteps' in self.config: - self.config['num_train_timesteps'] = _opt('schedulers_timesteps_range') + self.config['num_train_timesteps'] = get_override('schedulers_timesteps_range') if 'EDM' in name: del self.config['beta_start'] del self.config['beta_end']