From 738bc8e1291512a5cd8ec4834b2cb26a5ee682b5 Mon Sep 17 00:00:00 2001 From: Wagner Bruna Date: Mon, 30 Mar 2026 19:45:53 -0300 Subject: [PATCH] feat: add support for the eta parameter to ancestral samplers --- examples/cli/README.md | 4 ++-- examples/common/common.hpp | 4 ++-- examples/server/README.md | 4 ++-- src/denoiser.hpp | 14 ++++++++------ src/stable-diffusion.cpp | 26 +++++++++++++++++++++++++- 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/examples/cli/README.md b/examples/cli/README.md index 9b273a705..7bb037a96 100644 --- a/examples/cli/README.md +++ b/examples/cli/README.md @@ -114,7 +114,7 @@ Generation Options: medium --skip-layer-start SLG enabling point (default: 0.01) --skip-layer-end SLG disabling point (default: 0.2) - --eta eta in DDIM, only for DDIM and TCD (default: 0) + --eta noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and res_2s; 1 for euler_a and dpm++2s_a) --flow-shift shift value for Flow models like SD3.x or WAN (default: auto) --high-noise-cfg-scale (high noise) unconditional guidance scale: (default: 7.0) --high-noise-img-cfg-scale (high noise) image guidance scale for inpaint or instruct-pix2pix models (default: same as --cfg-scale) @@ -122,7 +122,7 @@ Generation Options: --high-noise-slg-scale (high noise) skip layer guidance (SLG) scale, only for DiT models: (default: 0) --high-noise-skip-layer-start (high noise) SLG enabling point (default: 0.01) --high-noise-skip-layer-end (high noise) SLG disabling point (default: 0.2) - --high-noise-eta (high noise) eta in DDIM, only for DDIM and TCD (default: 0) + --high-noise-eta (high noise) noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and res_2s; 1 for euler_a and dpm++2s_a) --strength strength for noising/unnoising (default: 0.75) --pm-style-strength --control-strength strength to apply Control Net (default: 0.9). 1.0 corresponds to full destruction of information in init image diff --git a/examples/common/common.hpp b/examples/common/common.hpp index 4735cb0e2..b170df300 100644 --- a/examples/common/common.hpp +++ b/examples/common/common.hpp @@ -1131,7 +1131,7 @@ struct SDGenerationParams { &sample_params.guidance.slg.layer_end}, {"", "--eta", - "eta in DDIM, only for DDIM and TCD (default: 0)", + "noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and res_2s; 1 for euler_a and dpm++2s_a)", &sample_params.eta}, {"", "--flow-shift", @@ -1163,7 +1163,7 @@ struct SDGenerationParams { &high_noise_sample_params.guidance.slg.layer_end}, {"", "--high-noise-eta", - "(high noise) eta in DDIM, only for DDIM and TCD (default: 0)", + "(high noise) noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and res_2s; 1 for euler_a and dpm++2s_a)", &high_noise_sample_params.eta}, {"", "--strength", diff --git a/examples/server/README.md b/examples/server/README.md index 855e58ace..620586d2e 100644 --- a/examples/server/README.md +++ b/examples/server/README.md @@ -189,7 +189,7 @@ Default Generation Options: medium --skip-layer-start SLG enabling point (default: 0.01) --skip-layer-end SLG disabling point (default: 0.2) - --eta eta in DDIM, only for DDIM and TCD (default: 0) + --eta noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and res_2s; 1 for euler_a and dpm++2s_a) --flow-shift shift value for Flow models like SD3.x or WAN (default: auto) --high-noise-cfg-scale (high noise) unconditional guidance scale: (default: 7.0) --high-noise-img-cfg-scale (high noise) image guidance scale for inpaint or instruct-pix2pix models (default: same as --cfg-scale) @@ -197,7 +197,7 @@ Default Generation Options: --high-noise-slg-scale (high noise) skip layer guidance (SLG) scale, only for DiT models: (default: 0) --high-noise-skip-layer-start (high noise) SLG enabling point (default: 0.01) --high-noise-skip-layer-end (high noise) SLG disabling point (default: 0.2) - --high-noise-eta (high noise) eta in DDIM, only for DDIM and TCD (default: 0) + --high-noise-eta (high noise) noise multiplier (default: 0 for ddim_trailing, tcd, res_multistep and res_2s; 1 for euler_a and dpm++2s_a) --strength strength for noising/unnoising (default: 0.75) --pm-style-strength --control-strength strength to apply Control Net (default: 0.9). 1.0 corresponds to full destruction of information in init image diff --git a/src/denoiser.hpp b/src/denoiser.hpp index 1629b1a8e..2bc18be3f 100644 --- a/src/denoiser.hpp +++ b/src/denoiser.hpp @@ -789,7 +789,8 @@ static std::pair get_ancestral_step(float sigma_from, static sd::Tensor sample_euler_ancestral(denoise_cb_t model, sd::Tensor x, const std::vector& sigmas, - std::shared_ptr rng) { + std::shared_ptr rng, + float eta) { int steps = static_cast(sigmas.size()) - 1; for (int i = 0; i < steps; i++) { float sigma = sigmas[i]; @@ -799,7 +800,7 @@ static sd::Tensor sample_euler_ancestral(denoise_cb_t model, } sd::Tensor denoised = std::move(denoised_opt); sd::Tensor d = (x - denoised) / sigma; - auto [sigma_down, sigma_up] = get_ancestral_step(sigmas[i], sigmas[i + 1]); + auto [sigma_down, sigma_up] = get_ancestral_step(sigmas[i], sigmas[i + 1], eta); x += d * (sigma_down - sigmas[i]); if (sigmas[i + 1] > 0) { x += sd::Tensor::randn_like(x, rng) * sigma_up; @@ -885,7 +886,8 @@ static sd::Tensor sample_dpm2(denoise_cb_t model, static sd::Tensor sample_dpmpp_2s_ancestral(denoise_cb_t model, sd::Tensor x, const std::vector& sigmas, - std::shared_ptr rng) { + std::shared_ptr rng, + float eta) { auto t_fn = [](float sigma) -> float { return -log(sigma); }; auto sigma_fn = [](float t) -> float { return exp(-t); }; @@ -896,7 +898,7 @@ static sd::Tensor sample_dpmpp_2s_ancestral(denoise_cb_t model, return {}; } sd::Tensor denoised = std::move(denoised_opt); - auto [sigma_down, sigma_up] = get_ancestral_step(sigmas[i], sigmas[i + 1]); + auto [sigma_down, sigma_up] = get_ancestral_step(sigmas[i], sigmas[i + 1], eta); if (sigma_down == 0) { x = denoised; @@ -1371,7 +1373,7 @@ static sd::Tensor sample_k_diffusion(sample_method_t method, float eta) { switch (method) { case EULER_A_SAMPLE_METHOD: - return sample_euler_ancestral(model, std::move(x), sigmas, rng); + return sample_euler_ancestral(model, std::move(x), sigmas, rng, eta); case EULER_SAMPLE_METHOD: return sample_euler(model, std::move(x), sigmas); case HEUN_SAMPLE_METHOD: @@ -1379,7 +1381,7 @@ static sd::Tensor sample_k_diffusion(sample_method_t method, case DPM2_SAMPLE_METHOD: return sample_dpm2(model, std::move(x), sigmas); case DPMPP2S_A_SAMPLE_METHOD: - return sample_dpmpp_2s_ancestral(model, std::move(x), sigmas, rng); + return sample_dpmpp_2s_ancestral(model, std::move(x), sigmas, rng, eta); case DPMPP2M_SAMPLE_METHOD: return sample_dpmpp_2m(model, std::move(x), sigmas); case DPMPP2Mv2_SAMPLE_METHOD: diff --git a/src/stable-diffusion.cpp b/src/stable-diffusion.cpp index 093bed20e..3c9700fb4 100644 --- a/src/stable-diffusion.cpp +++ b/src/stable-diffusion.cpp @@ -2225,6 +2225,7 @@ void sd_sample_params_init(sd_sample_params_t* sample_params) { sample_params->scheduler = SCHEDULER_COUNT; sample_params->sample_method = SAMPLE_METHOD_COUNT; sample_params->sample_steps = 20; + sample_params->eta = INFINITY; sample_params->custom_sigmas = nullptr; sample_params->custom_sigmas_count = 0; sample_params->flow_shift = INFINITY; @@ -2438,6 +2439,25 @@ static scheduler_t resolve_scheduler(sd_ctx_t* sd_ctx, return scheduler; } +static float resolve_eta(sd_ctx_t* sd_ctx, + float eta, + enum sample_method_t sample_method) { + if (eta == INFINITY) { + switch(sample_method) { + case DDIM_TRAILING_SAMPLE_METHOD: + case TCD_SAMPLE_METHOD: + case RES_MULTISTEP_SAMPLE_METHOD: + case RES_2S_SAMPLE_METHOD: + return 0.0f; + case EULER_A_SAMPLE_METHOD: + case DPMPP2S_A_SAMPLE_METHOD: + return 1.0f; + default: ; + } + } + return eta; +} + struct GenerationRequest { std::string prompt; std::string negative_prompt; @@ -2586,6 +2606,7 @@ struct GenerationRequest { struct SamplePlan { enum sample_method_t sample_method = SAMPLE_METHOD_COUNT; enum sample_method_t high_noise_sample_method = SAMPLE_METHOD_COUNT; + float eta = 0.f; int sample_steps = 0; int high_noise_sample_steps = 0; int total_steps = 0; @@ -2597,6 +2618,7 @@ struct SamplePlan { const sd_img_gen_params_t* sd_img_gen_params, const GenerationRequest& request) { sample_method = sd_img_gen_params->sample_params.sample_method; + eta = sd_img_gen_params->sample_params.eta; sample_steps = sd_img_gen_params->sample_params.sample_steps; resolve(sd_ctx, &request, &sd_img_gen_params->sample_params); } @@ -2644,6 +2666,8 @@ struct SamplePlan { sd_ctx->sd->version); } + eta = resolve_eta(sd_ctx, eta, sample_method); + if (high_noise_sample_steps < 0) { for (size_t i = 0; i < sigmas.size(); ++i) { if (sigmas[i] < moe_boundary) { @@ -3123,7 +3147,7 @@ SD_API sd_image_t* generate_image(sd_ctx_t* sd_ctx, const sd_img_gen_params_t* s latents.control_image, request.control_strength, request.guidance, - request.eta, + plan.eta, request.shifted_timestep, plan.sample_method, plan.sigmas,