From e783981f3d7a546ce9ed4e4f093e4fa8d77fabeb Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Tue, 9 Jun 2026 22:10:32 +0100 Subject: [PATCH 1/2] fix(samplers): honor fallback setting in remaining sampler fallback paths create_sampler restored the model default scheduler on a prediction-type mismatch, on an unknown sampler config, and on any scheduler-constructor exception regardless of schedulers_fallback; only SD_SAMPLER_DEBUG could turn the prediction mismatch into an error. Raise like the other capability gates when the fallback setting is disabled. An unresolved sampler name substituted UniPC before any of those gates could run; pass the requested name through instead, so it falls back to the model default (or raises when fallback is disabled) and the infotext records Default rather than the unresolved name. find_sampler now also resolves an unspecified sampler to Default instead of UniPC, matching the platform default used everywhere else. --- modules/processing_helpers.py | 10 +++++----- modules/sd_samplers.py | 16 +++++++++------- modules/sd_samplers_diffusers.py | 2 ++ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py index 1135ceccc..1d81bbd56 100644 --- a/modules/processing_helpers.py +++ b/modules/processing_helpers.py @@ -584,9 +584,9 @@ def update_sampler(p, sd_model, second_pass=False): if sampler_selection == 'None': return sampler = sd_samplers.find_sampler(sampler_selection) - if sampler is None: - log.warning(f'Sampler: "{sampler_selection}" not found') - sampler = sd_samplers.all_samplers_map.get("UniPC") + resolved = sampler is not None + if not resolved: + log.warning(f'Sampler: name="{sampler_selection}" not found') sched_override_keys = [ 'schedulers_prediction_type', 'schedulers_beta_schedule', 'schedulers_timesteps', 'schedulers_sigma', 'schedulers_use_thresholding', 'schedulers_use_loworder', @@ -596,8 +596,8 @@ def update_sampler(p, sd_model, second_pass=False): 'schedulers_timestep_spacing', 'schedulers_timesteps_range', ] scheduler_overrides = {k: getattr(p, k) for k in sched_override_keys if getattr(p, k, None) is not None} - sampler = sd_samplers.create_sampler(sampler.name, sd_model, scheduler_overrides=scheduler_overrides) - if sampler is None or sampler_selection == 'Default': + sampler = sd_samplers.create_sampler(sampler.name if resolved else sampler_selection, sd_model, scheduler_overrides=scheduler_overrides) + if sampler is None or not resolved or sampler_selection == 'Default': if second_pass: p.hr_sampler = 'Default' else: diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index d38ab0acf..ef46719ba 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -15,7 +15,7 @@ loaded_config = None def find_sampler(name:str): if name is None or name == 'None': - return all_samplers_map.get("UniPC", None) + return all_samplers_map.get("Default", None) for sampler in all_samplers: if sampler.name.lower() == name.lower() or name in sampler.aliases: return sampler @@ -107,6 +107,8 @@ def create_sampler(name, model, scheduler_overrides=None): config = find_sampler_config(name) if config is None or config.constructor is None: + if debug or not shared.opts.schedulers_fallback: + raise ValueError(f'Sampler: name="{name}" unknown') return restore_default(model, name) from modules import sd_samplers_diffusers @@ -126,16 +128,16 @@ def create_sampler(name, model, scheduler_overrides=None): pass elif (model is not None) and (is_flow and not requires_flow): log.error(f'Sampler: "{sampler.name}" cls={sampler.sampler.__class__.__name__} pipe={model.__class__.__name__} type={pred_type} model requires sampler with discrete prediction') - if not debug: - return restore_default(model, name) - else: + if debug or not shared.opts.schedulers_fallback: raise ValueError(f'Sampler: name="{sampler.name}" cls={sampler.sampler.__class__.__name__} type={pred_type} model requires sampler with discrete prediction') + else: + return restore_default(model, name) elif (model is not None) and (not is_flow and requires_flow): log.error(f'Sampler: "{sampler.name}" cls={sampler.sampler.__class__.__name__} pipe={model.__class__.__name__} type={pred_type} model requires sampler with flow prediction') - if not debug: - return restore_default(model, name) - else: + if debug or not shared.opts.schedulers_fallback: raise ValueError(f'Sampler: name="{sampler.name}" cls={sampler.sampler.__class__.__name__} type={pred_type} model requires sampler with flow prediction') + else: + return restore_default(model, name) # assign sampler if model is not None: diff --git a/modules/sd_samplers_diffusers.py b/modules/sd_samplers_diffusers.py index a6e10f63a..e69e426a7 100644 --- a/modules/sd_samplers_diffusers.py +++ b/modules/sd_samplers_diffusers.py @@ -519,6 +519,8 @@ class DiffusionSampler: log.error(f'Sampler: "{name}" {e}') if debug: errors.display(e, 'Samplers') + if debug or not shared.opts.schedulers_fallback: + raise self.sampler = None return From 47aa9a2c93fd95b2a90170117f1ea264c50cf792 Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Wed, 10 Jun 2026 01:27:38 +0100 Subject: [PATCH 2/2] fix(samplers): report validation failures without backtrace Sampler capability gates raised plain ValueError when schedulers_fallback is disabled, so the API middleware and the gradio call wrapper printed a full backtrace for an expected outcome. Add errors.ValidationError, raise it from the gates, and report it message-only in errors.display; the UI error box and the API error response already carry the message. --- modules/errors.py | 7 +++++++ modules/sd_samplers.py | 8 ++++---- modules/sd_samplers_diffusers.py | 10 +++++----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/modules/errors.py b/modules/errors.py index 32bf3f9dd..5faf6f9b7 100644 --- a/modules/errors.py +++ b/modules/errors.py @@ -10,6 +10,10 @@ install_traceback() already_displayed = {} +class ValidationError(ValueError): + """Expected validation failure: display() reports the message without a traceback.""" + + def install(suppress=None): if suppress is None: suppress = [] @@ -23,6 +27,9 @@ def display(e: Exception, task: str, suppress=None): suppress = [] if isinstance(e, ErrorLimiterAbort): return + if isinstance(e, ValidationError): + log.error(f"{task or 'error'}: {e}") + return log.error(f"{task or 'error'}: {type(e).__name__}") """ trace = traceback.format_exc() diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index ef46719ba..9fbdf0a5a 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -1,6 +1,6 @@ import os import copy -from modules import shared +from modules import shared, errors from modules.logger import log @@ -108,7 +108,7 @@ def create_sampler(name, model, scheduler_overrides=None): if config is None or config.constructor is None: if debug or not shared.opts.schedulers_fallback: - raise ValueError(f'Sampler: name="{name}" unknown') + raise errors.ValidationError(f'Sampler: name="{name}" unknown') return restore_default(model, name) from modules import sd_samplers_diffusers @@ -129,13 +129,13 @@ def create_sampler(name, model, scheduler_overrides=None): elif (model is not None) and (is_flow and not requires_flow): log.error(f'Sampler: "{sampler.name}" cls={sampler.sampler.__class__.__name__} pipe={model.__class__.__name__} type={pred_type} model requires sampler with discrete prediction') if debug or not shared.opts.schedulers_fallback: - raise ValueError(f'Sampler: name="{sampler.name}" cls={sampler.sampler.__class__.__name__} type={pred_type} model requires sampler with discrete prediction') + raise errors.ValidationError(f'Sampler: name="{sampler.name}" cls={sampler.sampler.__class__.__name__} type={pred_type} model requires sampler with discrete prediction') else: return restore_default(model, name) elif (model is not None) and (not is_flow and requires_flow): log.error(f'Sampler: "{sampler.name}" cls={sampler.sampler.__class__.__name__} pipe={model.__class__.__name__} type={pred_type} model requires sampler with flow prediction') if debug or not shared.opts.schedulers_fallback: - raise ValueError(f'Sampler: name="{sampler.name}" cls={sampler.sampler.__class__.__name__} type={pred_type} model requires sampler with flow prediction') + raise errors.ValidationError(f'Sampler: name="{sampler.name}" cls={sampler.sampler.__class__.__name__} type={pred_type} model requires sampler with flow prediction') else: return restore_default(model, name) diff --git a/modules/sd_samplers_diffusers.py b/modules/sd_samplers_diffusers.py index e69e426a7..043eda07a 100644 --- a/modules/sd_samplers_diffusers.py +++ b/modules/sd_samplers_diffusers.py @@ -450,7 +450,7 @@ class DiffusionSampler: sigma_applied = True if not sigma_applied: if debug or not shared.opts.schedulers_fallback: - raise ValueError(f'Sampler: name="{name}" does not support sigma="{sched_sigma}"') + raise errors.ValidationError(f'Sampler: name="{name}" does not support sigma="{sched_sigma}"') else: log.warning(f'Sampler: name="{name}" does not support sigma="{sched_sigma}", using default schedule') else: @@ -520,7 +520,7 @@ class DiffusionSampler: if debug: errors.display(e, 'Samplers') if debug or not shared.opts.schedulers_fallback: - raise + raise errors.ValidationError(f'Sampler: name="{name}" {e}') from e self.sampler = None return @@ -529,7 +529,7 @@ class DiffusionSampler: cls_source = inspect.getsource(constructor) if '"flow_prediction"' not in cls_source and "'flow_prediction'" not in cls_source: if debug or not shared.opts.schedulers_fallback: - raise ValueError(f'Sampler: name="{name}" does not appear to support flow_prediction') + raise errors.ValidationError(f'Sampler: name="{name}" does not appear to support flow_prediction') else: log.warning(f'Sampler: name="{name}" does not support flow_prediction') self.sampler = None @@ -549,7 +549,7 @@ class DiffusionSampler: default_accept_sigmas = (model is not None) and hasattr(model.default_scheduler, 'set_timesteps') and "sigmas" in set(inspect.signature(model.default_scheduler.set_timesteps).parameters.keys()) if default_accept_sigmas and not accept_sigmas: if debug or not shared.opts.schedulers_fallback: - raise ValueError(f'Sampler: name="{name}" does not accept sigmas') + raise errors.ValidationError(f'Sampler: name="{name}" does not accept sigmas') else: log.warning(f'Sampler: name="{name}" does not accept sigmas') self.sampler = None @@ -559,7 +559,7 @@ class DiffusionSampler: if default_accept_scale_noise and not accept_scale_noise: log.warning(f'Sampler: name="{name}" does not implement scale noise') if debug or not shared.opts.schedulers_fallback: - raise ValueError(f'Sampler: name="{name}" does not implement scale noise') + raise errors.ValidationError(f'Sampler: name="{name}" does not implement scale noise') else: log.warning(f'Sampler: name="{name}" does not implement scale noise') self.sampler = None