From 4105a773766ce4f926f8f8c8e4e8efad34956df3 Mon Sep 17 00:00:00 2001 From: Wagner Bruna Date: Tue, 24 Mar 2026 08:18:55 -0300 Subject: [PATCH 1/2] refactor: group sigma_up and sigma_down calculations --- src/denoiser.hpp | 65 +++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/src/denoiser.hpp b/src/denoiser.hpp index b92ca4e3f..19a07d34b 100644 --- a/src/denoiser.hpp +++ b/src/denoiser.hpp @@ -761,6 +761,26 @@ struct Flux2FlowDenoiser : public FluxFlowDenoiser { typedef std::function denoise_cb_t; +static void generate_ancestral_step(float& sigma_up, float& sigma_down, float sigma_from, float sigma_to, float eta = 1.0f) { + // sigma_up = min(sigma_to, eta * √(sigma_to² * (sigma_from² - sigma_to²) / sigma_from²)) + // sigma_down = √(sigma_to² - sigma_sup²) + sigma_up = 0.0f; + sigma_down = sigma_to; + if (eta > 0.0f) { + float sigma_from_sq = sigma_from * sigma_from; + float sigma_to_sq = sigma_to * sigma_to; + if (sigma_from_sq > 0.0f) { + float term = sigma_to_sq * (sigma_from_sq - sigma_to_sq) / sigma_from_sq; + if (term > 0.0f) { + sigma_up = eta * std::sqrt(term); + } + } + sigma_up = std::min(sigma_up, sigma_to); + float sigma_down_sq = sigma_to_sq - sigma_up * sigma_up; + sigma_down = sigma_down_sq > 0.0f ? std::sqrt(sigma_down_sq) : 0.0f; + } +} + // k diffusion reverse ODE: dx = (x - D(x;\sigma)) / \sigma dt; \sigma(t) = t static bool sample_k_diffusion(sample_method_t method, denoise_cb_t model, @@ -797,9 +817,8 @@ static bool sample_k_diffusion(sample_method_t method, } // get_ancestral_step - float sigma_up = std::min(sigmas[i + 1], - std::sqrt(sigmas[i + 1] * sigmas[i + 1] * (sigmas[i] * sigmas[i] - sigmas[i + 1] * sigmas[i + 1]) / (sigmas[i] * sigmas[i]))); - float sigma_down = std::sqrt(sigmas[i + 1] * sigmas[i + 1] - sigma_up * sigma_up); + float sigma_up, sigma_down; + generate_ancestral_step(sigma_up, sigma_down, sigmas[i], sigmas[i + 1]); // Euler method float dt = sigma_down - sigmas[i]; @@ -990,9 +1009,8 @@ static bool sample_k_diffusion(sample_method_t method, } // get_ancestral_step - float sigma_up = std::min(sigmas[i + 1], - std::sqrt(sigmas[i + 1] * sigmas[i + 1] * (sigmas[i] * sigmas[i] - sigmas[i + 1] * sigmas[i + 1]) / (sigmas[i] * sigmas[i]))); - float sigma_down = std::sqrt(sigmas[i + 1] * sigmas[i + 1] - sigma_up * sigma_up); + float sigma_up, sigma_down; + generate_ancestral_step(sigma_up, sigma_down, sigmas[i], sigmas[i + 1]); auto t_fn = [](float sigma) -> float { return -log(sigma); }; auto sigma_fn = [](float t) -> float { return exp(-t); }; @@ -1719,22 +1737,8 @@ static bool sample_k_diffusion(sample_method_t method, float sigma_from = sigmas[i]; float sigma_to = sigmas[i + 1]; - float sigma_up = 0.0f; - float sigma_down = sigma_to; - - if (eta > 0.0f) { - float sigma_from_sq = sigma_from * sigma_from; - float sigma_to_sq = sigma_to * sigma_to; - if (sigma_from_sq > 0.0f) { - float term = sigma_to_sq * (sigma_from_sq - sigma_to_sq) / sigma_from_sq; - if (term > 0.0f) { - sigma_up = eta * std::sqrt(term); - } - } - sigma_up = std::min(sigma_up, sigma_to); - float sigma_down_sq = sigma_to_sq - sigma_up * sigma_up; - sigma_down = sigma_down_sq > 0.0f ? std::sqrt(sigma_down_sq) : 0.0f; - } + float sigma_up, sigma_down; + generate_ancestral_step(sigma_up, sigma_down, sigma_from, sigma_to, eta); if (sigma_down == 0.0f || !have_old_sigma) { float dt = sigma_down - sigma_from; @@ -1826,21 +1830,8 @@ static bool sample_k_diffusion(sample_method_t method, return false; } - float sigma_up = 0.0f; - float sigma_down = sigma_to; - if (eta > 0.0f) { - float sigma_from_sq = sigma_from * sigma_from; - float sigma_to_sq = sigma_to * sigma_to; - if (sigma_from_sq > 0.0f) { - float term = sigma_to_sq * (sigma_from_sq - sigma_to_sq) / sigma_from_sq; - if (term > 0.0f) { - sigma_up = eta * std::sqrt(term); - } - } - sigma_up = std::min(sigma_up, sigma_to); - float sigma_down_sq = sigma_to_sq - sigma_up * sigma_up; - sigma_down = sigma_down_sq > 0.0f ? std::sqrt(sigma_down_sq) : 0.0f; - } + float sigma_up, sigma_down; + generate_ancestral_step(sigma_up, sigma_down, sigma_from, sigma_to, eta); float* vec_x = (float*)x->data; float* vec_x0 = (float*)x0->data; From dcd9d6a9c53cf2528b9126b503f1a3ac9886fd20 Mon Sep 17 00:00:00 2001 From: Wagner Bruna Date: Tue, 24 Mar 2026 08:19:09 -0300 Subject: [PATCH 2/2] 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 | 8 ++++---- src/stable-diffusion.cpp | 29 +++++++++++++++++++++++++++-- 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/examples/cli/README.md b/examples/cli/README.md index 904f3c441..5fca0285e 100644 --- a/examples/cli/README.md +++ b/examples/cli/README.md @@ -109,7 +109,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) @@ -117,7 +117,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 9389b03a3..29aa99c66 100644 --- a/examples/common/common.hpp +++ b/examples/common/common.hpp @@ -1197,7 +1197,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", @@ -1229,7 +1229,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 8aa2158f5..9ba3dfd1f 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 19a07d34b..d0902e7e7 100644 --- a/src/denoiser.hpp +++ b/src/denoiser.hpp @@ -818,7 +818,7 @@ static bool sample_k_diffusion(sample_method_t method, // get_ancestral_step float sigma_up, sigma_down; - generate_ancestral_step(sigma_up, sigma_down, sigmas[i], sigmas[i + 1]); + generate_ancestral_step(sigma_up, sigma_down, sigmas[i], sigmas[i + 1], eta); // Euler method float dt = sigma_down - sigmas[i]; @@ -832,7 +832,7 @@ static bool sample_k_diffusion(sample_method_t method, } } - if (sigmas[i + 1] > 0) { + if (sigmas[i + 1] > 0 && sigma_up > 0.0f) { // x = x + noise_sampler(sigmas[i], sigmas[i + 1]) * s_noise * sigma_up ggml_ext_im_set_randn_f32(noise, rng); // noise = load_tensor_from_file(work_ctx, "./rand" + std::to_string(i+1) + ".bin"); @@ -1010,7 +1010,7 @@ static bool sample_k_diffusion(sample_method_t method, // get_ancestral_step float sigma_up, sigma_down; - generate_ancestral_step(sigma_up, sigma_down, sigmas[i], sigmas[i + 1]); + generate_ancestral_step(sigma_up, sigma_down, sigmas[i], sigmas[i + 1], eta); auto t_fn = [](float sigma) -> float { return -log(sigma); }; auto sigma_fn = [](float t) -> float { return exp(-t); }; @@ -1053,7 +1053,7 @@ static bool sample_k_diffusion(sample_method_t method, } // Noise addition - if (sigmas[i + 1] > 0) { + if (sigmas[i + 1] > 0 && sigma_up > 0.0f) { ggml_ext_im_set_randn_f32(noise, rng); { float* vec_x = (float*)x->data; diff --git a/src/stable-diffusion.cpp b/src/stable-diffusion.cpp index bbf2f979d..ddbc331b6 100644 --- a/src/stable-diffusion.cpp +++ b/src/stable-diffusion.cpp @@ -2777,6 +2777,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; @@ -2953,6 +2954,21 @@ enum sample_method_t sd_get_default_sample_method(const sd_ctx_t* sd_ctx) { return EULER_A_SAMPLE_METHOD; } +static float sd_get_default_eta(enum sample_method_t sample_method) { + 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 INFINITY; + } +} + enum scheduler_t sd_get_default_scheduler(const sd_ctx_t* sd_ctx, enum sample_method_t sample_method) { if (sd_ctx != nullptr && sd_ctx->sd != nullptr) { auto edm_v_denoiser = std::dynamic_pointer_cast(sd_ctx->sd->denoiser); @@ -3331,7 +3347,16 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, const sd_img_gen_params_t* sd_img_g if (sample_method == SAMPLE_METHOD_COUNT) { sample_method = sd_get_default_sample_method(sd_ctx); } - LOG_INFO("sampling using %s method", sampling_methods_str[sample_method]); + float eta = sd_img_gen_params->sample_params.eta; + float default_eta = sd_get_default_eta(sample_method); + if (default_eta != INFINITY) { + if (eta == INFINITY) { + eta = default_eta; + } + LOG_INFO("sampling using %s method (eta %g)", sampling_methods_str[sample_method], eta); + } else { + LOG_INFO("sampling using %s method", sampling_methods_str[sample_method]); + } int sample_steps = sd_img_gen_params->sample_params.sample_steps; std::vector sigmas; @@ -3546,7 +3571,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, const sd_img_gen_params_t* sd_img_g SAFE_STR(sd_img_gen_params->negative_prompt), sd_img_gen_params->clip_skip, guidance, - sd_img_gen_params->sample_params.eta, + eta, sd_img_gen_params->sample_params.shifted_timestep, width, height,