From 91d6b080247251778aec30c964af51804e3b1318 Mon Sep 17 00:00:00 2001 From: vmobilis <75476228+vmobilis@users.noreply.github.com> Date: Wed, 5 Aug 2026 08:38:09 +0300 Subject: [PATCH 1/2] denoiser.hpp: configurable lms.max_order --- src/runtime/denoiser.hpp | 63 ++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/src/runtime/denoiser.hpp b/src/runtime/denoiser.hpp index f58f42b55..09910ac97 100644 --- a/src/runtime/denoiser.hpp +++ b/src/runtime/denoiser.hpp @@ -2583,26 +2583,45 @@ static sd::Tensor sample_lms(denoise_cb_t model, const std::vector& sigmas, const SamplerExtraArgs& extra_sample_args) { // Linear Multi-Step from https://github.com/crowsonkb/k-diffusion - int divisions = 1000; + int max_order = 4; + int shift = 1; // 4. 0 - as in original, 4, 1 - produced in PR #1843 for (const auto& [key, value] : extra_sample_args) { int parsed = 0; + if (key == "lms_max_order") { + if (!parse_strict_int(value, parsed)) { + LOG_WARN("ignoring invalid lms extra sample arg '%s=%s'", key.c_str(), value.c_str()); + continue; + } + max_order = std::max(1, parsed); + // smaller values make result closer to Euler + // bigger values need more steps + } + if (key == "lms_shift") { + if (!parse_strict_int(value, parsed)) { + LOG_WARN("ignoring invalid lms extra sample arg '%s=%s'", key.c_str(), value.c_str()); + continue; + } + shift = std::max(0, parsed); + // opposite to max_order, + // higher values need less steps but behaves closer to Euler + } if (key == "lms_divisions") { if (!parse_strict_int(value, parsed)) { LOG_WARN("ignoring invalid lms extra sample arg '%s=%s'", key.c_str(), value.c_str()); continue; } divisions = parsed; // std::max(1, parsed); - // values above 35M produce noise, can be fixed by double precision // values < 1 always produce noise + // values above 35M require double precision in integral } } - LOG_DEBUG("linear multi-step sampler: integrating using %i division%s", divisions, (divisions == 1) ? "" : "s"); auto linear_multistep_coeff = [=](const int order, const int m, const int j) -> float { if (!divisions) return sigmas[m + 1] - sigmas[m]; // delta / 0 * 0 -#define LMS_PRECISION float // double +#define LMS_PRECISION float //double // can be used if max_order < 10 + // and divisions are less than 30-35 millions const LMS_PRECISION a = sigmas[m], dx = (sigmas[m + 1] - a) / divisions, s = sigmas[m - j]; const LMS_PRECISION b0 = a + 0.5f * dx; // using Riemann middle integral LMS_PRECISION sum = 0.0f; @@ -2622,11 +2641,12 @@ static sd::Tensor sample_lms(denoise_cb_t model, return sum * dx; }; - const int max_order = 4; - float lms_coeff[max_order]; + int steps = static_cast(sigmas.size()) - 1; + max_order = std::min(max_order, steps); // history can not be larger than steps + LOG_DEBUG("linear multi-step sampler: max_order = %i, shift = %i, divisions = %i", max_order, shift, divisions); + std::vector lms_coeff(max_order); std::vector> hist = {}; - int steps = static_cast(sigmas.size()) - 1; for (int i = 0; i < steps; i++) { const float sigma = sigmas[i]; @@ -2637,25 +2657,26 @@ static sd::Tensor sample_lms(denoise_cb_t model, sd::Tensor denoised = std::move(denoised_opt.pred); const int order = std::min(max_order, i + 1); + for (int c = 0; c < order; c++) // computing coefficients lms_coeff[c] = linear_multistep_coeff(order, i, c); sd::Tensor d_cur = (x - denoised) / sigma; - switch (order) { - case 4: // derivative + 3 history points - x += hist[hist.size() - 2] * lms_coeff[3]; - case 3: - x += hist[hist.size() - 1] * lms_coeff[2]; - case 2: - x += hist.back() * lms_coeff[1]; - case 1: - x += d_cur * lms_coeff[0]; - } - - if (hist.size() == static_cast(max_order - 1)) { - hist.erase(hist.begin()); + x += d_cur * lms_coeff[0]; + if (max_order > 1) { // if max_order == 1, history is not used (order always < 2) + int hist_size_p1 = hist.size() + 1; + if (i) { // history does not exist at 1st step + int hist_max = hist.size() - 1; + for (int c = 2; c <= order; c++) + x += hist[std::min(hist_max, hist_size_p1 - c + shift)] * lms_coeff[c - 1]; + // max_order == 4 => hist[] index = 2, 1, 0 + // shift == 1 => hist[] index = 2, 2, 1 + } + if (hist_size_p1 == max_order) { + hist.erase(hist.begin()); + } + hist.push_back(std::move(d_cur)); } - hist.push_back(std::move(d_cur)); } return x; } From 6c20b5cf3b5b921e7e196cbec82ff09c59da5bbc Mon Sep 17 00:00:00 2001 From: vmobilis <75476228+vmobilis@users.noreply.github.com> Date: Sat, 15 Aug 2026 12:24:58 +0300 Subject: [PATCH 2/2] denoiser.hpp: coefficients below `shift` are unused --- src/runtime/denoiser.hpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/runtime/denoiser.hpp b/src/runtime/denoiser.hpp index 09910ac97..ac1afd6d9 100644 --- a/src/runtime/denoiser.hpp +++ b/src/runtime/denoiser.hpp @@ -2582,10 +2582,11 @@ static sd::Tensor sample_lms(denoise_cb_t model, sd::Tensor x, const std::vector& sigmas, const SamplerExtraArgs& extra_sample_args) { - // Linear Multi-Step from https://github.com/crowsonkb/k-diffusion + // Linear Multi-Step from https://github.com/crowsonkb/k-diffusion, + // modified with "history shift" value, which seemingly needs less steps int divisions = 1000; int max_order = 4; - int shift = 1; // 4. 0 - as in original, 4, 1 - produced in PR #1843 + int shift = 1; // 4. 0 - original, 4, 1 - PR leejet#1843, 3, 1 - smoother image for (const auto& [key, value] : extra_sample_args) { int parsed = 0; if (key == "lms_max_order") { @@ -2594,8 +2595,9 @@ static sd::Tensor sample_lms(denoise_cb_t model, continue; } max_order = std::max(1, parsed); - // smaller values make result closer to Euler - // bigger values need more steps + // smaller values make the result softer, closer to Euler + // higher values need more steps + // values above 12 can produce NaNs, depending on steps and scheduler } if (key == "lms_shift") { if (!parse_strict_int(value, parsed)) { @@ -2603,8 +2605,8 @@ static sd::Tensor sample_lms(denoise_cb_t model, continue; } shift = std::max(0, parsed); - // opposite to max_order, - // higher values need less steps but behaves closer to Euler + // for a low number of steps, the value 1 works best + // not needed for karras and exponential schedulers } if (key == "lms_divisions") { if (!parse_strict_int(value, parsed)) { @@ -2613,15 +2615,17 @@ static sd::Tensor sample_lms(denoise_cb_t model, } divisions = parsed; // std::max(1, parsed); // values < 1 always produce noise - // values above 35M require double precision in integral + // values above 30M require double precision in the integrator + // (they are needless and just slow the integration down, but + // with single precision they softly produce noise + // near the 35M, it can be used for distorted generations) } } auto linear_multistep_coeff = [=](const int order, const int m, const int j) -> float { if (!divisions) return sigmas[m + 1] - sigmas[m]; // delta / 0 * 0 -#define LMS_PRECISION float //double // can be used if max_order < 10 - // and divisions are less than 30-35 millions +#define LMS_PRECISION float // when divisions > 30 millions, the double precision fixes noise const LMS_PRECISION a = sigmas[m], dx = (sigmas[m + 1] - a) / divisions, s = sigmas[m - j]; const LMS_PRECISION b0 = a + 0.5f * dx; // using Riemann middle integral LMS_PRECISION sum = 0.0f; @@ -2643,7 +2647,7 @@ static sd::Tensor sample_lms(denoise_cb_t model, int steps = static_cast(sigmas.size()) - 1; max_order = std::min(max_order, steps); // history can not be larger than steps - LOG_DEBUG("linear multi-step sampler: max_order = %i, shift = %i, divisions = %i", max_order, shift, divisions); + LOG_DEBUG("linear multi-step sampler: lms_max_order = %i, lms_shift = %i, lms_divisions = %i", max_order, shift, divisions); std::vector lms_coeff(max_order); std::vector> hist = {}; @@ -2658,12 +2662,12 @@ static sd::Tensor sample_lms(denoise_cb_t model, const int order = std::min(max_order, i + 1); - for (int c = 0; c < order; c++) // computing coefficients + for (int c = shift; c < order; c++) // computing coefficients lms_coeff[c] = linear_multistep_coeff(order, i, c); sd::Tensor d_cur = (x - denoised) / sigma; x += d_cur * lms_coeff[0]; - if (max_order > 1) { // if max_order == 1, history is not used (order always < 2) + if (max_order > 1) { // if max_order == 1, the history is not used (order always < 2) int hist_size_p1 = hist.size() + 1; if (i) { // history does not exist at 1st step int hist_max = hist.size() - 1;