From ea602f9de04260fdf2bd96443a3ed9bccbb4f43e Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Thu, 4 Jun 2026 02:02:59 +0300 Subject: [PATCH] Revert "Distilled Flow: add Karras stochastic churn (zero-overhead variability)" This reverts commit b47452e76e7fdfb8204051f2888d95d85e60816c. --- CHANGELOG.md | 2 -- conditioning.py | 3 -- docs/FunPackStudio.md | 2 +- samplers.py | 73 ++++++++----------------------------------- web/funpack_studio.js | 19 ++--------- 5 files changed, 16 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0047f2c..df87d064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,6 @@ ### Added -Added **Karras-style stochastic churn** to the `FunPack Distilled Flow` sampler (`s_churn`, `s_tmin`, `s_tmax`) for variability and detail at zero extra model evaluations. Each step optionally re-noises the latent within `[s_tmin, s_tmax]` before the model eval, then denoises from the bumped sigma level — the model is still evaluated once per step, just at a slightly higher noise level. This keeps the distilled trajectory from freezing (more motion, less stale/locked imagery) and can sharpen by letting the model re-resolve. `sigma_hat` is clamped to the schedule start so the model never sees a higher-than-trained noise level, and the final jump-to-zero detail step is never churned. Default band `0.5`–`1.0` churns the structure/motion sigmas while protecting the sub-0.4 detail finish (per the empirical sigma findings). Churn supersedes the legacy `s_noise` ancestral path while active, and is video-stream-only on packed LTXAV latents. With `s_churn=0` the sampler is byte-identical to the previous deterministic ODE. Wired into Studio's **Distilled Flow** sampler config panel. - Ported **velocity bias + reactive rescue** to the `FunPack Distilled Flow` sampler (`sample_funpack_distilled_flow`), reusing the same capture/memory/rescue machinery as the Hybrid Euler-2S and LTXAV/RF samplers. The sampler now exposes `velocity_bias_mode`, `velocity_bias_strength`, `velocity_bias_source`, `velocity_refinement_key`, `rescue_mode`, `rescue_threshold`, and `rescue_strength`, and these are wired into Studio's **Distilled Flow** sampler config panel (with the same blank/`default` velocity-key → wired-refinement-key fallback the Hybrid panel uses). Steering is applied magnitude-preservingly and, on packed LTXAV latents, confined to the video stream (audio-safe). Note: few-step distilled schedules may only land on one or two velocity targets, so apply/rescue fire less often than on an 8-step run; they no-op cleanly when no target matches. With everything off the sampler is byte-identical to the previous deterministic ODE. ## [2.7.7] - 2026-06-03 diff --git a/conditioning.py b/conditioning.py index 47e80eba..66eb8d34 100644 --- a/conditioning.py +++ b/conditioning.py @@ -13652,9 +13652,6 @@ class FunPackStudio: order=int(dc.get("order", 2)), final_correction_steps=int(dc.get("final_correction_steps", 1)), s_noise=float(dc.get("s_noise", 0.0)), - s_churn=float(dc.get("s_churn", 0.0)), - s_tmin=float(dc.get("s_tmin", 0.5)), - s_tmax=float(dc.get("s_tmax", 1.0)), velocity_bias_mode=str(dc.get("velocity_bias_mode", "off")), velocity_bias_strength=float(dc.get("velocity_bias_strength", 0.0)), velocity_bias_source=str(dc.get("velocity_bias_source", "mean")), diff --git a/docs/FunPackStudio.md b/docs/FunPackStudio.md index 3eb48794..2028e98f 100644 --- a/docs/FunPackStudio.md +++ b/docs/FunPackStudio.md @@ -124,7 +124,7 @@ Configure two independent sampler outputs: **High Pass** and **Low Pass**. Each **Sampler types:** - `Hybrid Euler 2S` - ancestral Euler with order-2 extrapolation for motion, late DPM-Solver++ 2S for quality (Heun corrector on rectified-flow/LTXAV models). Exposes eta, eta_final, s_noise, quality phase settings, motion pulse, velocity bias, and reactive rescue controls. -- `Distilled Flow` - ODE sampler for few-step distilled models. Exposes order, final correction steps, and s_noise, plus **Karras churn** (`s_churn` / `s_tmin` / `s_tmax`) for zero-overhead variability and detail — it re-noises within the chosen sigma band before each eval and re-resolves, fighting stale/frozen output while protecting the sub-0.4 detail finish (default band 0.5–1.0). It also exposes the same velocity bias and reactive rescue controls as the Hybrid sampler (sharing the same trajectory memory). On few-step schedules velocity/rescue fire less often than on an 8-step run, since fewer steps land on a velocity target. +- `Distilled Flow` - ODE sampler for few-step distilled models. Exposes order, final correction steps, and s_noise, plus the same velocity bias and reactive rescue controls as the Hybrid sampler (sharing the same trajectory memory). On few-step schedules these fire less often than on an 8-step run, since fewer steps land on a velocity target. - `KSampler` - any standard ComfyUI sampler by name. **Sigmas** are entered as a comma-separated float list. Leave empty to pass sigmas in externally. diff --git a/samplers.py b/samplers.py index 306c212f..af92d0e1 100644 --- a/samplers.py +++ b/samplers.py @@ -1429,7 +1429,6 @@ class FunPackHybridEuler2SSampler: def sample_funpack_distilled_flow(model, x, sigmas, extra_args=None, callback=None, disable=None, order=2, s_noise=0.0, final_correction_steps=1, - s_churn=0.0, s_tmin=0.5, s_tmax=1.0, velocity_bias_mode="off", velocity_bias_strength=0.0, velocity_bias_source="mean", velocity_refinement_key="default", rescue_mode=False, rescue_threshold=0.15, rescue_strength=0.2, @@ -1446,12 +1445,6 @@ def sample_funpack_distilled_flow(model, x, sigmas, extra_args=None, callback=No sharpness and detail in the steps that define the final output. - Optional s_noise: tiny ancestral-style noise injection for diversity. Default 0 = fully deterministic ODE (recommended for distilled models). - - Optional Karras-style stochastic churn (s_churn over [s_tmin, s_tmax]): - re-noises x BEFORE the eval, then denoises from the bumped sigma_hat. Zero - extra model calls (still one eval per step, at a slightly higher noise level). - Keeps the distilled trajectory from freezing — more motion / less stale - imagery — and can sharpen by letting the model re-resolve. The final - jump-to-zero detail step is never churned. - Velocity-bias capture/apply and reactive rescue around each model eval — the same memory machinery the Hybrid/RF samplers use. On few-step schedules the velocity targets may only match a step or two, so apply/rescue fire less @@ -1467,10 +1460,6 @@ def sample_funpack_distilled_flow(model, x, sigmas, extra_args=None, callback=No order = max(1, min(2, int(order))) s_noise = max(0.0, min(0.5, float(s_noise))) - s_churn = max(0.0, float(s_churn)) - s_tmin = max(0.0, float(s_tmin)) - s_tmax = float(s_tmax) - sigma0 = float(sigmas[0].item()) final_correction_steps = max(0, min(total_steps // 2, int(final_correction_steps))) correction_start_idx = total_steps - final_correction_steps @@ -1496,23 +1485,6 @@ def sample_funpack_distilled_flow(model, x, sigmas, extra_args=None, callback=No sigma_next = sigmas[i + 1] velocity_target = _velocity_bias_target(sigmas, sigma) - # Karras-style stochastic churn: re-noise within [s_tmin, s_tmax] BEFORE the eval, - # then denoise from the bumped sigma_hat. Zero extra model calls. sigma_hat is clamped - # to the schedule start so we never push the model past its trained noise level, and - # the final jump-to-zero step (sigma_next == 0) is never churned. Audio-safe via - # video_mask. With s_churn == 0, sigma_hat stays == sigma and this block is a no-op. - sigma_hat = sigma - if s_churn > 0.0 and sigma_next > 0: - s_cur = float(sigma.item()) - if s_tmin <= s_cur <= s_tmax: - gamma = min(s_churn / total_steps, 2.0 ** 0.5 - 1.0) - sh = min(s_cur * (1.0 + gamma), sigma0) - if sh > s_cur: - x_pre = x - x = x + noise_sampler(sigma, sigma_next) * math.sqrt(sh * sh - s_cur * s_cur) - x = _video_only(x, x_pre, video_mask) - sigma_hat = sigma.new_tensor(float(sh)) - if _velocity_bias_enabled(velocity_bias_mode, "apply"): x_pre = x x = _apply_velocity_bias(x, velocity_refinement_key, velocity_target, velocity_bias_strength, @@ -1520,21 +1492,21 @@ def sample_funpack_distilled_flow(model, x, sigmas, extra_args=None, callback=No prompt_sig=rescue_prompt_sig, source=velocity_bias_source) x = _video_only(x, x_pre, video_mask) - denoised = model(x, sigma_hat * s_in, **extra_args) + denoised = model(x, sigma * s_in, **extra_args) if rescue_mode or _velocity_bias_enabled(velocity_bias_mode, "capture"): - _capture_velocity_bias(velocity_refinement_key, velocity_target, x, sigma_hat, denoised, prompt_sig=rescue_prompt_sig) + _capture_velocity_bias(velocity_refinement_key, velocity_target, x, sigma, denoised, prompt_sig=rescue_prompt_sig) if rescue_mode and velocity_target is not None: denoised = _video_only(_rescue_denoised( - denoised, x, sigma_hat, velocity_refinement_key, + denoised, x, sigma, velocity_refinement_key, velocity_target, rescue_threshold, rescue_strength, prompt_sig=rescue_prompt_sig, source=velocity_bias_source, ), denoised, video_mask) if callback is not None: - callback({"x": x, "i": i, "sigma": sigma, "sigma_hat": sigma_hat, "denoised": denoised}) + callback({"x": x, "i": i, "sigma": sigma, "sigma_hat": sigma, "denoised": denoised}) - h = float((sigma_hat - sigma_next).abs().item()) + h = float((sigma - sigma_next).abs().item()) # Adams-Bashforth 2-step multistep correction. # Coefficients for variable step sizes: r = h_current / h_previous. @@ -1557,12 +1529,12 @@ def sample_funpack_distilled_flow(model, x, sigmas, extra_args=None, callback=No x = denoised_eff continue - dt = sigma_next - sigma_hat # negative: sigmas decrease (from the churned level) + dt = sigma_next - sigma # negative: sigmas decrease if i >= correction_start_idx: # Heun predictor-corrector. # Predictor: Euler step using the (multistep-corrected) denoised. - d1 = k_diffusion_sampling.to_d(x, sigma_hat, denoised_eff) + d1 = k_diffusion_sampling.to_d(x, sigma, denoised_eff) x_pred = x + d1 * dt # Corrector: evaluate model at the predicted x and sigma_next. denoised_pred = model(x_pred, sigma_next * s_in, **extra_args) @@ -1572,13 +1544,10 @@ def sample_funpack_distilled_flow(model, x, sigmas, extra_args=None, callback=No prev_denoised = None prev_h = None else: - d = k_diffusion_sampling.to_d(x, sigma_hat, denoised_eff) + d = k_diffusion_sampling.to_d(x, sigma, denoised_eff) x = x + d * dt - # Legacy post-step ancestral noise. Skipped when churn is active (churn is the - # stochastic source then), so the two never stack. When s_churn == 0 this path is - # unchanged and sigma_hat == sigma, keeping the deterministic ODE byte-identical. - if s_noise > 0.0 and s_churn <= 0.0: - sigma_up = math.sqrt(max(0.0, float(sigma_hat.item()) ** 2 - float(sigma_next.item()) ** 2)) + if s_noise > 0.0: + sigma_up = math.sqrt(max(0.0, float(sigma.item()) ** 2 - float(sigma_next.item()) ** 2)) if sigma_up > 0.0: x = x + noise_sampler(sigma, sigma_next) * s_noise * sigma_up @@ -1609,19 +1578,7 @@ class FunPackDistilledFlowSampler: "min": 0.0, "max": 0.50, "step": 0.01, - "tooltip": "Optional post-step ancestral noise for diversity. 0 = fully deterministic ODE (recommended). Small values (0.05–0.15) add variation without strongly disrupting the distilled trajectory. Ignored when s_churn > 0 (churn becomes the stochastic source).", - }), - "s_churn": ("FLOAT", { - "default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1, - "tooltip": "Karras-style stochastic churn: re-noises before each eval within [s_tmin, s_tmax], then denoises from the bumped sigma. Zero extra model calls. Fights stale/frozen distilled output (more motion, less locked imagery) and can sharpen. 0 = off. Try 1-4; 4+ saturates. Spread across steps internally.", - }), - "s_tmin": ("FLOAT", { - "default": 0.5, "min": 0.0, "max": 1.0, "step": 0.025, - "tooltip": "Lowest sigma that churn touches. Default 0.5 protects the sub-0.4 detail-finish steps (re-noising there smears fine detail). Lower it only if you want churn to reach the detail end.", - }), - "s_tmax": ("FLOAT", { - "default": 1.0, "min": 0.0, "max": 1.0, "step": 0.025, - "tooltip": "Highest sigma that churn touches. Default 1.0 = up to the schedule start. Churn at/above the start sigma is clamped to the start, so the model never sees a higher-than-trained noise level.", + "tooltip": "Optional stochastic noise for diversity. 0 = fully deterministic ODE (recommended). Small values (0.05–0.15) add variation without strongly disrupting the distilled trajectory.", }), "velocity_bias_mode": (VELOCITY_BIAS_MODES, { "default": "off", @@ -1664,12 +1621,11 @@ class FunPackDistilledFlowSampler: DESCRIPTION = ( "ODE sampler for distilled few-step video models (e.g. LTX2.3 distilled LoRA). " "Adams-Bashforth 2-step multistep for better trajectory accuracy across large sigma jumps, " - "Heun predictor-corrector on final steps for quality, optional controlled noise and " - "Karras churn for diversity, and optional velocity bias + reactive rescue (shared with the Hybrid/RF samplers)." + "Heun predictor-corrector on final steps for quality, optional controlled noise for diversity, " + "and optional velocity bias + reactive rescue (shared with the Hybrid/RF samplers)." ) def get_sampler(self, order=2, final_correction_steps=1, s_noise=0.0, - s_churn=0.0, s_tmin=0.5, s_tmax=1.0, velocity_bias_mode="off", velocity_bias_strength=0.0, velocity_bias_source="mean", velocity_refinement_key="default", rescue_mode=False, rescue_threshold=0.15, rescue_strength=0.2, @@ -1681,9 +1637,6 @@ class FunPackDistilledFlowSampler: "order": order, "final_correction_steps": final_correction_steps, "s_noise": s_noise, - "s_churn": s_churn, - "s_tmin": s_tmin, - "s_tmax": s_tmax, "velocity_bias_mode": velocity_bias_mode, "velocity_bias_strength": velocity_bias_strength, "velocity_bias_source": velocity_bias_source, diff --git a/web/funpack_studio.js b/web/funpack_studio.js index 66f1367b..89d69ef3 100644 --- a/web/funpack_studio.js +++ b/web/funpack_studio.js @@ -74,8 +74,8 @@ function defaultSettings() { loras: [], loras_config: { mode: "ltx2", per_block: false }, samplers: { - high: { type: "Hybrid Euler 2S", sigmas: "", hybrid: { eta: 1.0, eta_final: 1.0, s_noise: 1.0, high_quality_pct: 0.35, correction_blend: 1.0, quality_sharpness: 0.0, motion_pulse_mode: "off", motion_pulse_start_pct: 0.3, motion_pulse_count: 2, motion_pulse_spacing_pct: 0.22, motion_pulse_strength: 0.85, velocity_bias_mode: "off", velocity_bias_strength: 0.0, velocity_bias_source: "mean", velocity_refinement_key: "default", rescue_mode: false, rescue_threshold: 0.15, rescue_strength: 0.2 }, distilled: { order: 2, final_correction_steps: 1, s_noise: 0.0, s_churn: 0.0, s_tmin: 0.5, s_tmax: 1.0, velocity_bias_mode: "off", velocity_bias_strength: 0.0, velocity_bias_source: "mean", velocity_refinement_key: "default", rescue_mode: false, rescue_threshold: 0.15, rescue_strength: 0.2 }, ksampler_name: "euler" }, - low: { type: "Distilled Flow", sigmas: "", hybrid: { eta: 1.0, eta_final: 1.0, s_noise: 1.0, high_quality_pct: 0.35, correction_blend: 1.0, quality_sharpness: 0.0, motion_pulse_mode: "off", motion_pulse_start_pct: 0.3, motion_pulse_count: 2, motion_pulse_spacing_pct: 0.22, motion_pulse_strength: 0.85, velocity_bias_mode: "off", velocity_bias_strength: 0.0, velocity_bias_source: "mean", velocity_refinement_key: "default", rescue_mode: false, rescue_threshold: 0.15, rescue_strength: 0.2 }, distilled: { order: 2, final_correction_steps: 1, s_noise: 0.0, s_churn: 0.0, s_tmin: 0.5, s_tmax: 1.0, velocity_bias_mode: "off", velocity_bias_strength: 0.0, velocity_bias_source: "mean", velocity_refinement_key: "default", rescue_mode: false, rescue_threshold: 0.15, rescue_strength: 0.2 }, ksampler_name: "euler" }, + high: { type: "Hybrid Euler 2S", sigmas: "", hybrid: { eta: 1.0, eta_final: 1.0, s_noise: 1.0, high_quality_pct: 0.35, correction_blend: 1.0, quality_sharpness: 0.0, motion_pulse_mode: "off", motion_pulse_start_pct: 0.3, motion_pulse_count: 2, motion_pulse_spacing_pct: 0.22, motion_pulse_strength: 0.85, velocity_bias_mode: "off", velocity_bias_strength: 0.0, velocity_bias_source: "mean", velocity_refinement_key: "default", rescue_mode: false, rescue_threshold: 0.15, rescue_strength: 0.2 }, distilled: { order: 2, final_correction_steps: 1, s_noise: 0.0, velocity_bias_mode: "off", velocity_bias_strength: 0.0, velocity_bias_source: "mean", velocity_refinement_key: "default", rescue_mode: false, rescue_threshold: 0.15, rescue_strength: 0.2 }, ksampler_name: "euler" }, + low: { type: "Distilled Flow", sigmas: "", hybrid: { eta: 1.0, eta_final: 1.0, s_noise: 1.0, high_quality_pct: 0.35, correction_blend: 1.0, quality_sharpness: 0.0, motion_pulse_mode: "off", motion_pulse_start_pct: 0.3, motion_pulse_count: 2, motion_pulse_spacing_pct: 0.22, motion_pulse_strength: 0.85, velocity_bias_mode: "off", velocity_bias_strength: 0.0, velocity_bias_source: "mean", velocity_refinement_key: "default", rescue_mode: false, rescue_threshold: 0.15, rescue_strength: 0.2 }, distilled: { order: 2, final_correction_steps: 1, s_noise: 0.0, velocity_bias_mode: "off", velocity_bias_strength: 0.0, velocity_bias_source: "mean", velocity_refinement_key: "default", rescue_mode: false, rescue_threshold: 0.15, rescue_strength: 0.2 }, ksampler_name: "euler" }, }, }; } @@ -1497,21 +1497,6 @@ function openPanel(node) { const snInp = numInput(dc.s_noise, 0, 0.5, 0.01); snInp.addEventListener("input", () => { dc.s_noise = parseFloat(snInp.value); }); body.append(row("s_noise", snInp)); - const scInp = numInput(dc.s_churn ?? 0.0, 0, 100, 0.1); - scInp.addEventListener("input", () => { dc.s_churn = parseFloat(scInp.value); renderSampler(); }); - body.append(row("s_churn", scInp)); - if ((+dc.s_churn || 0) > 0) { - const stminInp = numInput(dc.s_tmin ?? 0.5, 0, 1, 0.025); - stminInp.addEventListener("input", () => { dc.s_tmin = parseFloat(stminInp.value); }); - body.append(row("churn s_tmin", stminInp)); - const stmaxInp = numInput(dc.s_tmax ?? 1.0, 0, 1, 0.025); - stmaxInp.addEventListener("input", () => { dc.s_tmax = parseFloat(stmaxInp.value); }); - body.append(row("churn s_tmax", stmaxInp)); - const scNote = document.createElement("div"); - scNote.style.cssText = "font-size:11px;opacity:0.7;margin:0 0 4px 0;"; - scNote.textContent = "Re-noises before each eval inside [s_tmin, s_tmax], then re-resolves — fights stale/frozen output (more motion) and can sharpen, at zero extra model calls. Default band 0.5–1.0 churns the structure/motion sigmas and protects the sub-0.4 detail finish. Supersedes s_noise while on."; - body.append(scNote); - } const dVbm = selectEl(VELOCITY_BIAS_MODES, dc.velocity_bias_mode || "off"); dVbm.addEventListener("change", () => { dc.velocity_bias_mode = dVbm.value; renderSampler(); }); body.append(row("velocity bias mode", dVbm));