From d460960889a506d782805582687f2fdd2fb34bf0 Mon Sep 17 00:00:00 2001 From: Wagner Bruna Date: Sun, 16 Nov 2025 19:04:17 -0300 Subject: [PATCH 1/2] feat: add LCM scheduler and use it by default for LCM sampling --- denoiser.hpp | 17 +++++++++++++++++ examples/cli/main.cpp | 8 ++++++-- stable-diffusion.cpp | 22 ++++++++++++++++------ stable-diffusion.h | 1 + 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/denoiser.hpp b/denoiser.hpp index 5ff45bb2c..3003fa2bc 100644 --- a/denoiser.hpp +++ b/denoiser.hpp @@ -251,6 +251,23 @@ struct SGMUniformSchedule : SigmaSchedule { } }; +struct LCMSchedule : SigmaSchedule { + std::vector get_sigmas(uint32_t n, float sigma_min, float sigma_max, t_to_sigma_t t_to_sigma) override { + std::vector result; + result.reserve(n + 1); + const int original_steps = 50; + const int k = TIMESTEPS / original_steps; + for (int i = 0; i < n; i++) { + // the rounding ensures we match the training schedule of the LCM model + int index = (i * original_steps) / n; + int timestep = (original_steps - index) * k - 1; + result.push_back(t_to_sigma(timestep)); + } + result.push_back(0.0f); + return result; + } +}; + struct KarrasSchedule : SigmaSchedule { std::vector get_sigmas(uint32_t n, float sigma_min, float sigma_max, t_to_sigma_t t_to_sigma) override { // These *COULD* be function arguments here, diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 3cfe9281a..1ffb8ca06 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -1169,7 +1169,7 @@ void parse_args(int argc, const char** argv, SDParams& params) { on_lora_apply_mode_arg}, {"", "--scheduler", - "denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits, smoothstep, sgm_uniform, simple], default: discrete", + "denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits, smoothstep, sgm_uniform, simple, lcm], default: discrete", on_schedule_arg}, {"", "--skip-layers", @@ -1182,7 +1182,7 @@ void parse_args(int argc, const char** argv, SDParams& params) { on_high_noise_sample_method_arg}, {"", "--high-noise-scheduler", - "(high noise) denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits, smoothstep, sgm_uniform, simple], default: discrete", + "(high noise) denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits, smoothstep, sgm_uniform, simple, lcm], default: discrete", on_high_noise_schedule_arg}, {"", "--high-noise-skip-layers", @@ -1826,6 +1826,10 @@ int main(int argc, const char* argv[]) { params.sample_params.sample_method = sd_get_default_sample_method(sd_ctx); } + if (params.sample_params.sample_method == LCM && params.sample_params.scheduler == DEFAULT) { + params.sample_params.scheduler = LCM_SCHEDULER; + } + if (params.mode == IMG_GEN) { sd_img_gen_params_t img_gen_params = { params.prompt.c_str(), diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 72aa33171..caa07ecec 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -905,6 +905,10 @@ class StableDiffusionGGML { LOG_INFO("Running with SmoothStep scheduler"); denoiser->scheduler = std::make_shared(); break; + case LCM_SCHEDULER: + LOG_INFO("Running with LCM scheduler"); + denoiser->scheduler = std::make_shared(); + break; case DEFAULT: // Don't touch anything. break; @@ -2206,6 +2210,7 @@ const char* schedule_to_str[] = { "sgm_uniform", "simple", "smoothstep", + "lcm", }; const char* sd_schedule_name(enum scheduler_t scheduler) { @@ -2969,7 +2974,17 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, const sd_img_gen_params_t* sd_img_g size_t t0 = ggml_time_ms(); - sd_ctx->sd->init_scheduler(sd_img_gen_params->sample_params.scheduler); + enum sample_method_t sample_method = sd_img_gen_params->sample_params.sample_method; + if (sample_method == SAMPLE_METHOD_DEFAULT) { + sample_method = sd_get_default_sample_method(sd_ctx); + } + + enum scheduler_t scheduler = sd_img_gen_params->sample_params.scheduler; + if (sample_method == LCM && scheduler == DEFAULT) { + scheduler = LCM_SCHEDULER; + } + + sd_ctx->sd->init_scheduler(scheduler); std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sample_steps); ggml_tensor* init_latent = nullptr; @@ -3158,11 +3173,6 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, const sd_img_gen_params_t* sd_img_g LOG_INFO("encode_first_stage completed, taking %.2fs", (t1 - t0) * 1.0f / 1000); } - enum sample_method_t sample_method = sd_img_gen_params->sample_params.sample_method; - if (sample_method == SAMPLE_METHOD_DEFAULT) { - sample_method = sd_get_default_sample_method(sd_ctx); - } - sd_image_t* result_images = generate_image_internal(sd_ctx, work_ctx, init_latent, diff --git a/stable-diffusion.h b/stable-diffusion.h index 6be85af22..97de031cb 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -62,6 +62,7 @@ enum scheduler_t { SGM_UNIFORM, SIMPLE, SMOOTHSTEP, + LCM_SCHEDULER, SCHEDULE_COUNT }; From 21b01da92f550f65aac4fb179303b96c62c84299 Mon Sep 17 00:00:00 2001 From: leejet Date: Sat, 22 Nov 2025 13:52:34 +0800 Subject: [PATCH 2/2] update docs --- examples/cli/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/cli/README.md b/examples/cli/README.md index 4de84c79f..e92336027 100644 --- a/examples/cli/README.md +++ b/examples/cli/README.md @@ -107,8 +107,8 @@ Options: compatibility issues with quantized parameters, but it usually offers faster inference speed and, in some cases, lower memory usage. The at_runtime mode, on the other hand, is exactly the opposite. - --scheduler denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits, smoothstep, sgm_uniform, simple], default: - discrete + --scheduler denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits, smoothstep, sgm_uniform, simple, lcm], + default: discrete --skip-layers layers to skip for SLG steps (default: [7,8,9]) --high-noise-sampling-method (high noise) sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing, tcd] default: euler for Flux/SD3/Wan, euler_a otherwise