diff --git a/src/diffusers/schedulers/scheduling_dpmsolver_multistep_inverse.py b/src/diffusers/schedulers/scheduling_dpmsolver_multistep_inverse.py index b236e9975fed..9c9a19fb2eb2 100644 --- a/src/diffusers/schedulers/scheduling_dpmsolver_multistep_inverse.py +++ b/src/diffusers/schedulers/scheduling_dpmsolver_multistep_inverse.py @@ -118,11 +118,10 @@ class DPMSolverMultistepInverseScheduler(SchedulerMixin, ConfigMixin): The threshold value for dynamic thresholding. Valid only when `thresholding=True` and `algorithm_type="dpmsolver++"`. algorithm_type (`str`, defaults to `dpmsolver++`): - Algorithm type for the solver; can be `dpmsolver`, `dpmsolver++`, `sde-dpmsolver` or `sde-dpmsolver++`. The - `dpmsolver` type implements the algorithms in the [DPMSolver](https://huggingface.co/papers/2206.00927) - paper, and the `dpmsolver++` type implements the algorithms in the - [DPMSolver++](https://huggingface.co/papers/2211.01095) paper. It is recommended to use `dpmsolver++` or - `sde-dpmsolver++` with `solver_order=2` for guided sampling like in Stable Diffusion. + Algorithm type for the solver; can be `dpmsolver` or `dpmsolver++`. The `dpmsolver` type implements the + algorithms in the [DPMSolver](https://huggingface.co/papers/2206.00927) paper, and the `dpmsolver++` type + implements the algorithms in the [DPMSolver++](https://huggingface.co/papers/2211.01095) paper. Stochastic + variants (`sde-dpmsolver`, `sde-dpmsolver++`) are not supported: inversion is deterministic. solver_type (`str`, defaults to `midpoint`): Solver type for the second-order solver; can be `midpoint` or `heun`. The solver type slightly affects the sample quality, especially for a small number of steps. It is recommended to use `midpoint` solvers. @@ -130,9 +129,8 @@ class DPMSolverMultistepInverseScheduler(SchedulerMixin, ConfigMixin): Whether to use lower-order solvers in the final steps. Only valid for < 15 inference steps. This can stabilize the sampling of DPMSolver for steps < 15, especially for steps <= 10. euler_at_final (`bool`, defaults to `False`): - Whether to use Euler's method in the final step. It is a trade-off between numerical stability and detail - richness. This can stabilize the sampling of the SDE variant of DPMSolver for small number of inference - steps, but sometimes may result in blurring. + Whether to use Euler's method (first-order) in the final step. It is a trade-off between numerical + stability and detail richness for a small number of inference steps, but sometimes may result in blurring. use_karras_sigmas (`bool`, *optional*, defaults to `False`): Whether to use Karras sigmas for step sizes in the noise schedule during the sampling process. If `True`, the sigmas are determined according to a sequence of noise levels {σi}. @@ -174,7 +172,7 @@ def __init__( thresholding: bool = False, dynamic_thresholding_ratio: float = 0.995, sample_max_value: float = 1.0, - algorithm_type: Literal["dpmsolver", "dpmsolver++", "sde-dpmsolver", "sde-dpmsolver++"] = "dpmsolver++", + algorithm_type: Literal["dpmsolver", "dpmsolver++"] = "dpmsolver++", solver_type: Literal["midpoint", "heun"] = "midpoint", lower_order_final: bool = True, euler_at_final: bool = False, @@ -188,6 +186,11 @@ def __init__( timestep_spacing: Literal["linspace", "leading", "trailing"] = "linspace", steps_offset: int = 0, ): + if algorithm_type in ["sde-dpmsolver", "sde-dpmsolver++"]: + raise ValueError( + f"DPMSolverMultistepInverseScheduler does not support algorithm_type='{algorithm_type}' because " + "inversion is deterministic; use 'dpmsolver++' or 'dpmsolver' instead." + ) if self.config.use_beta_sigmas and not is_scipy_available(): raise ImportError("Make sure to install scipy if you want to use beta sigmas.") if ( @@ -203,8 +206,11 @@ def __init__( raise ValueError( "Only one of `config.use_beta_sigmas`, `config.use_exponential_sigmas`, `config.use_karras_sigmas` can be used." ) - if algorithm_type in ["dpmsolver", "sde-dpmsolver"]: - deprecation_message = f"algorithm_type {algorithm_type} is deprecated and will be removed in a future version. Choose from `dpmsolver++` or `sde-dpmsolver++` instead" + if algorithm_type == "dpmsolver": + deprecation_message = ( + "algorithm_type dpmsolver is deprecated and will be removed in a future version. " + "Choose `dpmsolver++` instead." + ) deprecate( "algorithm_types dpmsolver and sde-dpmsolver", "1.0.0", @@ -244,12 +250,7 @@ def __init__( self.init_noise_sigma = 1.0 # settings for DPM-Solver - if algorithm_type not in [ - "dpmsolver", - "dpmsolver++", - "sde-dpmsolver", - "sde-dpmsolver++", - ]: + if algorithm_type not in ["dpmsolver", "dpmsolver++"]: if algorithm_type == "deis": self.register_to_config(algorithm_type="dpmsolver++") else: diff --git a/tests/schedulers/test_scheduler_dpm_multi_inverse.py b/tests/schedulers/test_scheduler_dpm_multi_inverse.py index 0eced957190c..f2c85e8b63b2 100644 --- a/tests/schedulers/test_scheduler_dpm_multi_inverse.py +++ b/tests/schedulers/test_scheduler_dpm_multi_inverse.py @@ -271,3 +271,27 @@ def test_beta_sigmas(self): def test_exponential_sigmas(self): self.check_over_configs(use_exponential_sigmas=True) + + def test_rejects_stochastic_algorithm_types(self): + # Inversion uses increasing sigmas, so h = lambda_t - lambda_s is negative. + # The SDE variance terms are only real for h > 0 and produce NaN under inversion. + for algorithm_type in ["sde-dpmsolver", "sde-dpmsolver++"]: + with self.assertRaises(ValueError) as ctx: + DPMSolverMultistepInverseScheduler(algorithm_type=algorithm_type) + message = str(ctx.exception) + self.assertIn("DPMSolverMultistepInverseScheduler", message) + self.assertIn(algorithm_type, message) + self.assertIn("deterministic", message) + + for algorithm_type in ["dpmsolver", "dpmsolver++"]: + scheduler = DPMSolverMultistepInverseScheduler( + num_train_timesteps=1000, + beta_start=0.00085, + beta_end=0.012, + algorithm_type=algorithm_type, + ) + scheduler.set_timesteps(10) + sample = torch.randn(1, 4, 8, 8) + model_output = torch.randn(1, 4, 8, 8) + prev = scheduler.step(model_output, scheduler.timesteps[0], sample).prev_sample + self.assertFalse(torch.isnan(prev).any())