Revert "Distilled Flow: add Karras stochastic churn (zero-overhead variability)"

This reverts commit b47452e76e7fdfb8204051f2888d95d85e60816c.
This commit is contained in:
digital-garbage
2026-06-04 02:02:59 +03:00
parent e56dcf2c39
commit ea602f9de0
5 changed files with 16 additions and 83 deletions
-2
View File
@@ -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
-3
View File
@@ -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")),
+1 -1
View File
@@ -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.
+13 -60
View File
@@ -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,
+2 -17
View File
@@ -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));