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/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}, 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}), 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()