From 0c0d455c11f56df424d7d607f78b5da8041182da Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Sun, 7 Jun 2026 06:57:07 +0100 Subject: [PATCH 1/3] fix(api): resolve sampler names case-insensitively to canonical form validate_sampler_name only matched the exact, case-sensitive name, so near-miss client names such as lowercase variants were rejected while an omitted name silently used the model scheduler via the Default sentinel. Fall back to find_sampler and return the canonical name so create_sampler applies the intended sampler; unknown names still return 404. Add an API test covering case-insensitive resolution and rejection of unknown names. --- modules/api/helpers.py | 12 +++++++++--- test/test-generation-api.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/modules/api/helpers.py b/modules/api/helpers.py index 0843f0615..78cd697d8 100644 --- a/modules/api/helpers.py +++ b/modules/api/helpers.py @@ -17,9 +17,15 @@ def register_upload_store(getter_fn): def validate_sampler_name(name): config = sd_samplers.all_samplers_map.get(name, None) - if config is None: - raise HTTPException(status_code=404, detail="Sampler not found") - return name + if config is not None: + return name + # accept case-insensitive and alias variants, returning the canonical name so the + # exact-match lookup in create_sampler resolves instead of silently using the model default + if isinstance(name, str) and name not in ('', 'None'): + sampler = sd_samplers.find_sampler(name) + if sampler is not None: + return sampler.name + raise HTTPException(status_code=404, detail="Sampler not found") def decode_base64_to_image(encoding, quiet=False): diff --git a/test/test-generation-api.py b/test/test-generation-api.py index 98ec18982..ca0888dbd 100644 --- a/test/test-generation-api.py +++ b/test/test-generation-api.py @@ -210,6 +210,36 @@ class GenerationAPITest: data, elapsed = self._txt2img({'sampler_name': sampler}) self._check_generation(data, f'generate_{sampler}', elapsed) + def test_sampler_name_resolution(self, available_samplers): + """Sampler name resolution: a case-insensitive name resolves to the canonical sampler + (and is applied, not silently swapped for the model default), while an unknown name is + rejected rather than falling back to the default scheduler.""" + self._category = 'samplers' + print("\n--- Sampler Name Resolution ---") + + if self._critical_error: + self.skip('sampler_lenient_case', self._critical_error) + self.skip('sampler_unknown_rejected', self._critical_error) + return + + canonical = next((s for s in ('Euler a', 'DPM++ 2M', 'UniPC') if s in available_samplers), None) + if canonical is None: + self.skip('sampler_lenient_case', 'no known sampler available') + else: + data, _ = self._txt2img({'sampler_name': canonical.lower()}) + if 'error' in data: + self.record(False, 'sampler_lenient_case', f"lowercase '{canonical.lower()}' rejected: {data}") + else: + resolved = canonical in self._get_info(data) + self.record(resolved, 'sampler_lenient_case', + f"'{canonical.lower()}' -> '{canonical}'" if resolved + else f"generated but '{canonical}' not in info (model default used?)") + + data, _ = self._txt2img({'sampler_name': 'ThisIsNotARealSampler'}) + rejected = 'error' in data + self.record(rejected, 'sampler_unknown_rejected', + 'unknown name rejected' if rejected else 'unknown name was NOT rejected') + # ========================================================================= # Tests: Color Grading Params # ========================================================================= @@ -577,6 +607,7 @@ class GenerationAPITest: # Samplers available = self.test_samplers_list() self.test_samplers_generate(available) + self.test_sampler_name_resolution(available) # Grading self.run_grading_tests() From 72b6691fd685a63eb50ad1fbb4b2f5250811217f Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Sun, 7 Jun 2026 06:57:07 +0100 Subject: [PATCH 2/3] fix(api): default img2img sampler_name to Default like txt2img img2img still defaulted sampler_name to UniPC after the txt2img default was switched to Default in 7fbac675f, so an img2img request that omits the sampler forced UniPC instead of keeping the model's own scheduler. The control endpoint already used Default; align img2img with both. --- modules/api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/api/models.py b/modules/api/models.py index b24a7991c..82895dfbf 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -302,7 +302,7 @@ ReqImg2Img = PydanticModelGenerator( StableDiffusionProcessingImg2Img, [ {"key": "sampler_index", "type": Union[int, str], "default": 0}, - {"key": "sampler_name", "type": str, "default": "UniPC"}, + {"key": "sampler_name", "type": str, "default": "Default"}, {"key": "hr_sampler_name", "type": str, "default": "Same as primary"}, {"key": "init_images", "type": list, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.5}, From 08b843401e628ae94b8ff728b2b8a9119ab9be2a Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Sun, 7 Jun 2026 06:57:07 +0100 Subject: [PATCH 3/3] fix(ui): correct schedulers_sigma option choices to match backend schedulers_sigma is a hidden OptionInfo registration; the visible control is the sampler-accordion dropdown. Its choices still listed the k-diffusion set, including polyexponential (which the diffusers backend does not support) and omitting betas, lambdas, and flowmatch. Align the list with what sd_samplers_diffusers accepts and the dropdown offers. The list feeds warn-only validation, so this clears a dead value and spurious debug logs, not user-facing behavior. --- modules/ui_definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_definitions.py b/modules/ui_definitions.py index 17c181e50..950308273 100644 --- a/modules/ui_definitions.py +++ b/modules/ui_definitions.py @@ -742,7 +742,7 @@ def create_settings(cmd_opts): "schedulers_solver_order": OptionInfo(0, "Solver order (where", gr.Slider, {"minimum": 0, "maximum": 5, "step": 1, "visible": False}), "schedulers_use_loworder": OptionInfo(True, "Use simplified solvers in final steps", gr.Checkbox, {"visible": False}), "schedulers_prediction_type": OptionInfo("default", "Override model prediction type", gr.Radio, {"choices": ["default", "epsilon", "sample", "v_prediction", "flow_prediction"], "visible": False}), - "schedulers_sigma": OptionInfo("default", "Sigma algorithm", gr.Radio, {"choices": ["default", "karras", "exponential", "polyexponential"], "visible": False}), + "schedulers_sigma": OptionInfo("default", "Sigma algorithm", gr.Radio, {"choices": ["default", "karras", "betas", "exponential", "lambdas", "flowmatch"], "visible": False}), "schedulers_beta_schedule": OptionInfo("default", "Beta schedule", gr.Dropdown, {"choices": ["default", "linear", "scaled_linear", "squaredcos_cap_v2", "sigmoid"], "visible": False}), "schedulers_use_thresholding": OptionInfo(False, "Use dynamic thresholding", gr.Checkbox, {"visible": False}), "schedulers_timestep_spacing": OptionInfo("default", "Timestep spacing", gr.Dropdown, {"choices": ["default", "linspace", "leading", "trailing"], "visible": False}),