Add FunPack Normalizing Sampler (video-only latent normalization)
New SAMPLER for distilled LTXAV at CFG=1: deterministic 1st-order euler
base (clean ddim look + clean audio) plus per-step video-only latent
normalization to counter overbaking / oversaturation / colour drift at
~zero overhead. Tracks the video latent's robust spread, anchors a
reference once x0 is meaningful (normalize_start_sigma), and compresses
inflated later steps (normalize_strength). Benefit-only, no-op when
healthy, audio never touched (whole-latent for single-stream LTXV).
Velocity bias + rescue included, video-only.
Node FunPackNormalizingSampler + Studio sampler option ("Normalizing")
wired through _build_one_sampler and the JS panel. Registered in __init__.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
951c61953d
commit
49e7517389
@@ -4,6 +4,8 @@
|
||||
|
||||
### Added
|
||||
|
||||
**FunPack Normalizing Sampler** — a new `SAMPLER` (selectable in Studio alongside Hybrid Euler 2S / Distilled Flow / KSampler) built for distilled few-step LTXAV at CFG=1, where the model is already extracted and there's no redundant CFG computation to exploit. It runs a clean deterministic 1st-order euler base (the "ddim look" + clean audio — no AB2/Heun/ancestral) and adds **video-only latent normalization**: each step it tracks the video latent's robust spread, anchors a reference once x0 is meaningful (`normalize_start_sigma`), and gently compresses any later step whose spread inflates past it (`normalize_strength`), countering the overbaking / oversaturation / colour-drift that distilled latents fall into. Benefit-only (never amplifies, no-op when the latent is healthy), ~zero overhead (a few masked reductions), and audio-safe by construction — the audio stream rides plain euler and the normalization never touches it (whole-latent for single-stream LTXV). Velocity bias + reactive rescue are available too, video-only. Node `FunPackNormalizingSampler` + Studio sampler panel.
|
||||
|
||||
**Distilled Flow `AB2 ramp` — graduated 2nd order.** New opt-in toggle (off by default) that, instead of applying full Adams-Bashforth 2-step extrapolation on every step, ramps the AB2 contribution linearly from 0→1 across the schedule: early/high-sigma steps stay near plain 1st-order euler (where the denoised estimate is rough and full AB2 tends to overshoot), late/detail steps get full AB2. Reuses the already-computed AB2 estimate, so **no extra model evals** — aimed at squeezing better quality out of low-step distilled runs. No effect at `order=1`. Exposed on the Distilled Flow sampler node and in Studio's sampler panel; audio stays on the plain euler path as always.
|
||||
|
||||
### Fixed
|
||||
|
||||
+5
-2
@@ -20,7 +20,7 @@ if __package__:
|
||||
FunPackVideoStitch,
|
||||
)
|
||||
from .model_management import FunPackApplyLoraWeights, FunPackLoraLoader
|
||||
from .samplers import FunPackHybridEuler2SSampler, FunPackDistilledFlowSampler, FunPackLTXAVSceneChainSampler
|
||||
from .samplers import FunPackHybridEuler2SSampler, FunPackDistilledFlowSampler, FunPackNormalizingSampler, FunPackLTXAVSceneChainSampler
|
||||
from .templates import FunPackRefinementKeyLoader, FunPackSceneBuilder
|
||||
try:
|
||||
from . import batch_training # noqa: F401 registers /funpack/batch/* routes
|
||||
@@ -59,10 +59,11 @@ else:
|
||||
FunPackApplyLoraWeights = None
|
||||
FunPackLoraLoader = None
|
||||
try:
|
||||
from samplers import FunPackHybridEuler2SSampler, FunPackDistilledFlowSampler, FunPackLTXAVSceneChainSampler
|
||||
from samplers import FunPackHybridEuler2SSampler, FunPackDistilledFlowSampler, FunPackNormalizingSampler, FunPackLTXAVSceneChainSampler
|
||||
except Exception:
|
||||
FunPackHybridEuler2SSampler = None
|
||||
FunPackDistilledFlowSampler = None
|
||||
FunPackNormalizingSampler = None
|
||||
FunPackLTXAVSceneChainSampler = None
|
||||
try:
|
||||
from templates import FunPackRefinementKeyLoader, FunPackSceneBuilder
|
||||
@@ -89,6 +90,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"FunPackSaveRefinementLatent": FunPackSaveRefinementLatent,
|
||||
"FunPackHybridEuler2SSampler": FunPackHybridEuler2SSampler,
|
||||
"FunPackDistilledFlowSampler": FunPackDistilledFlowSampler,
|
||||
"FunPackNormalizingSampler": FunPackNormalizingSampler,
|
||||
"FunPackLTXAVSceneChainSampler": FunPackLTXAVSceneChainSampler,
|
||||
"FunPackApplyLoraWeights": FunPackApplyLoraWeights,
|
||||
"FunPackLoraLoader": FunPackLoraLoader,
|
||||
@@ -114,6 +116,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"FunPackSaveRefinementLatent": "FunPack Save Refinement Latent",
|
||||
"FunPackHybridEuler2SSampler": "FunPack Hybrid Euler 2S Sampler",
|
||||
"FunPackDistilledFlowSampler": "FunPack Distilled Flow Sampler",
|
||||
"FunPackNormalizingSampler": "FunPack Normalizing Sampler",
|
||||
"FunPackLTXAVSceneChainSampler": "FunPack LTXAV Scene Chain Sampler",
|
||||
"FunPackApplyLoraWeights": "FunPack Apply LoRA Weights",
|
||||
"FunPackLoraLoader": "FunPack LoRA Loader",
|
||||
|
||||
@@ -13927,6 +13927,29 @@ class FunPackStudio:
|
||||
rescue_prompt_sig=prompt_sig,
|
||||
sigmas=sigmas_raw,
|
||||
)
|
||||
elif sampler_type == "Normalizing":
|
||||
try:
|
||||
from .samplers import FunPackNormalizingSampler
|
||||
except ImportError:
|
||||
from samplers import FunPackNormalizingSampler
|
||||
nc = cfg.get("normalizing", {}) if isinstance(cfg.get("normalizing"), dict) else {}
|
||||
nvkey = str(nc.get("velocity_refinement_key", "") or "").strip()
|
||||
if not nvkey or nvkey == "default":
|
||||
nvkey = str(refinement_key or "").strip() or "default"
|
||||
node = FunPackNormalizingSampler()
|
||||
sampler, out_sigmas = node.get_sampler(
|
||||
normalize_strength=float(nc.get("normalize_strength", 0.5)),
|
||||
normalize_start_sigma=float(nc.get("normalize_start_sigma", 0.9)),
|
||||
velocity_bias_mode=str(nc.get("velocity_bias_mode", "off")),
|
||||
velocity_bias_strength=float(nc.get("velocity_bias_strength", 0.0)),
|
||||
velocity_bias_source=str(nc.get("velocity_bias_source", "mean")),
|
||||
velocity_refinement_key=nvkey,
|
||||
rescue_mode=bool(nc.get("rescue_mode", False)),
|
||||
rescue_threshold=float(nc.get("rescue_threshold", 0.15)),
|
||||
rescue_strength=float(nc.get("rescue_strength", 0.2)),
|
||||
rescue_prompt_sig=prompt_sig,
|
||||
sigmas=sigmas_raw,
|
||||
)
|
||||
elif sampler_type == "KSampler":
|
||||
import comfy.samplers as _cs
|
||||
sampler_name = str(cfg.get("ksampler_name", "euler") or "euler")
|
||||
|
||||
+208
@@ -1676,6 +1676,214 @@ class FunPackDistilledFlowSampler:
|
||||
return (sampler, prepared_sigmas)
|
||||
|
||||
|
||||
def _normalize_video_denoised(denoised, video_mask, sigma, ref, strength,
|
||||
start_sigma, tolerance=0.05, beta=0.9):
|
||||
"""Keep the VIDEO latent's spread from drifting into overbaked / oversaturated ranges.
|
||||
|
||||
Operates on the denoised (x0) prediction, video stream only — audio is never touched, so it
|
||||
is audio-safe by construction. Once x0 is meaningful (sigma <= start_sigma) it anchors a
|
||||
reference spread (robust std over the video region) and gently compresses any later step
|
||||
whose spread inflates past it; an EMA lets the reference follow legitimate detail growth
|
||||
while resisting abrupt overbake spikes. Benefit-only: it never amplifies and is a no-op when
|
||||
the latent stays healthy. Cost is a few masked reductions per step (~zero overhead).
|
||||
|
||||
`ref` is a 1-element list holding the reference scale across steps (mutable state).
|
||||
"""
|
||||
if strength <= 0.0:
|
||||
return denoised
|
||||
try:
|
||||
s = float(sigma.item()) if hasattr(sigma, "item") else float(sigma)
|
||||
except Exception:
|
||||
s = 1.0
|
||||
if s > start_sigma:
|
||||
return denoised # skip the pure-noise plateau where the x0 estimate is not yet meaningful
|
||||
try:
|
||||
# Stats over the video region (whole tensor for single-stream LTXV, where mask is None).
|
||||
if video_mask is not None:
|
||||
m = video_mask
|
||||
w = m.sum().clamp_min(1.0)
|
||||
mean = (denoised * m).sum() / w
|
||||
var = (((denoised - mean) ** 2) * m).sum() / w
|
||||
else:
|
||||
mean = denoised.mean()
|
||||
var = ((denoised - mean) ** 2).mean()
|
||||
std_v = float(torch.sqrt(var.clamp_min(1e-12)).item())
|
||||
if ref[0] is None:
|
||||
ref[0] = std_v
|
||||
return denoised
|
||||
target = ref[0] * (1.0 + tolerance)
|
||||
if std_v > target and std_v > 1e-6:
|
||||
factor = 1.0 - strength * (1.0 - target / std_v) # = lerp(1, target/std, strength)
|
||||
normalized = mean + (denoised - mean) * factor
|
||||
denoised = _video_only(normalized, denoised, m)
|
||||
ref[0] = ref[0] * beta + target * (1.0 - beta)
|
||||
else:
|
||||
ref[0] = ref[0] * beta + std_v * (1.0 - beta)
|
||||
except Exception:
|
||||
return denoised
|
||||
return denoised
|
||||
|
||||
|
||||
def sample_funpack_normalizing(model, x, sigmas, extra_args=None, callback=None,
|
||||
disable=None,
|
||||
normalize_strength=0.5, normalize_start_sigma=0.9,
|
||||
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,
|
||||
rescue_prompt_sig=None):
|
||||
"""Deterministic 1st-order euler (rectified-flow) sampler + video-only latent normalization.
|
||||
|
||||
The plain euler base gives the clean 'ddim look' and clean audio (no AB2/Heun/ancestral, so
|
||||
audio rides the same plain euler update). On top, per-step latent normalization keeps the
|
||||
VIDEO latent's statistics in a healthy range to counter overbaking / oversaturation / colour
|
||||
drift — the closest thing to a free, benefit-only quality lever for distilled LTXAV at CFG=1.
|
||||
Velocity bias + reactive rescue are available too; everything except the deterministic euler
|
||||
update is confined to the video stream, so audio stays clean.
|
||||
"""
|
||||
extra_args = {} if extra_args is None else extra_args
|
||||
total_steps = max(0, len(sigmas) - 1)
|
||||
if total_steps <= 0:
|
||||
return x
|
||||
normalize_strength = max(0.0, min(1.0, float(normalize_strength)))
|
||||
normalize_start_sigma = max(0.0, min(1.0, float(normalize_start_sigma)))
|
||||
|
||||
_RESCUE_LOG["warned_no_memory"] = False
|
||||
_RESCUE_LOG["warned_no_prompt_match"] = False
|
||||
_RESCUE_LOG["fired"] = 0
|
||||
|
||||
video_mask = _packed_video_mask(model, x)
|
||||
if video_mask is not None:
|
||||
n_aud = int((video_mask < 0.5).sum().item())
|
||||
print(f"[FunPack AV] normalizing sampler: audio-safe (audio held deterministic on "
|
||||
f"{n_aud} of {video_mask.shape[-1]} packed dims; normalization video-only)")
|
||||
|
||||
s_in = x.new_ones([x.shape[0]])
|
||||
ref_scale = [None]
|
||||
|
||||
for i in comfy.utils.model_trange(total_steps, disable=disable):
|
||||
sigma = sigmas[i]
|
||||
sigma_next = sigmas[i + 1]
|
||||
velocity_target = _velocity_bias_target(sigmas, sigma)
|
||||
|
||||
if _velocity_bias_enabled(velocity_bias_mode, "apply"):
|
||||
x_pre = x
|
||||
x = _apply_velocity_bias(x, velocity_refinement_key, velocity_target, velocity_bias_strength,
|
||||
sigma_ratio=_sigma_ratio(sigmas, sigma),
|
||||
prompt_sig=rescue_prompt_sig, source=velocity_bias_source)
|
||||
x = _video_only(x, x_pre, video_mask)
|
||||
|
||||
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, denoised, prompt_sig=rescue_prompt_sig)
|
||||
if rescue_mode and velocity_target is not None:
|
||||
denoised = _video_only(_rescue_denoised(
|
||||
denoised, x, sigma, velocity_refinement_key,
|
||||
velocity_target, rescue_threshold, rescue_strength, prompt_sig=rescue_prompt_sig,
|
||||
source=velocity_bias_source,
|
||||
), denoised, video_mask)
|
||||
|
||||
# The point of this sampler: video-only latent normalization (anti-overbake).
|
||||
denoised = _normalize_video_denoised(
|
||||
denoised, video_mask, sigma, ref_scale, normalize_strength, normalize_start_sigma,
|
||||
)
|
||||
|
||||
if callback is not None:
|
||||
callback({"x": x, "i": i, "sigma": sigma, "sigma_hat": sigma, "denoised": denoised})
|
||||
|
||||
if sigma_next == 0:
|
||||
x = denoised
|
||||
continue
|
||||
dt = sigma_next - sigma # negative: sigmas decrease
|
||||
d = k_diffusion_sampling.to_d(x, sigma, denoised)
|
||||
x = x + d * dt
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class FunPackNormalizingSampler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"normalize_strength": ("FLOAT", {
|
||||
"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.05,
|
||||
"tooltip": "How hard to pull the video latent's spread back toward its healthy reference when it inflates (overbaking). 0 = plain euler, no normalization. 0.5 = gentle anti-overbake. 1.0 = clamp spread to the reference. Audio is never affected.",
|
||||
}),
|
||||
"normalize_start_sigma": ("FLOAT", {
|
||||
"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.025,
|
||||
"tooltip": "Sigma at/below which normalization activates and the reference spread is anchored. Above this (pure-noise plateau) the x0 estimate is meaningless, so it's skipped. ~0.9 captures the reference at the structure-forming step.",
|
||||
}),
|
||||
"velocity_bias_mode": (VELOCITY_BIAS_MODES, {
|
||||
"default": "off",
|
||||
"tooltip": "Experimental: capture/apply averaged early model velocity (action) around the structure sigma. Off preserves the plain euler trajectory. Shares the same memory as the Hybrid/Distilled samplers.",
|
||||
}),
|
||||
"velocity_bias_strength": ("FLOAT", {
|
||||
"default": 0.0, "min": 0.0, "max": 3.0, "step": 0.05,
|
||||
"tooltip": "Strength of the remembered velocity (action) injected at the structure sigma. 0 disables. Creative/action tool, not a consistency lever.",
|
||||
}),
|
||||
"velocity_bias_source": (["mean", "nearest"], {
|
||||
"default": "mean",
|
||||
"tooltip": "How velocity bias / rescue pick a good direction. 'mean' = prompt-blind global average. 'nearest' = best-matching prompt cluster.",
|
||||
}),
|
||||
"velocity_refinement_key": ("STRING", {
|
||||
"default": "default", "multiline": False,
|
||||
"tooltip": "Memory key used to capture/apply velocity bias and rescue trajectories.",
|
||||
}),
|
||||
"rescue_mode": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Reactive in-flight rescue, rating-gated. Steers eligible steps toward trajectories you rated good and away from Awful ones (matched to the current prompt). No-op until a few gens are rated. Video-only.",
|
||||
}),
|
||||
"rescue_threshold": ("FLOAT", {
|
||||
"default": 0.15, "min": 0.0, "max": 1.0, "step": 0.01,
|
||||
"tooltip": "Fires when the step diverged from the good trajectory by more than this (1 - cosine) OR aligned with a bad one by more than this. Lower = corrects more eagerly.",
|
||||
}),
|
||||
"rescue_strength": ("FLOAT", {
|
||||
"default": 0.2, "min": 0.0, "max": 0.5, "step": 0.01,
|
||||
"tooltip": "How hard to pull toward good / away from bad when triggered (magnitude preserved). 0.5 is a strong correction.",
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"sigmas": ("SIGMAS",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SAMPLER", "SIGMAS")
|
||||
RETURN_NAMES = ("sampler", "sigmas")
|
||||
FUNCTION = "get_sampler"
|
||||
CATEGORY = "FunPack/Sampling"
|
||||
DESCRIPTION = (
|
||||
"Deterministic 1st-order euler (rectified-flow) sampler with video-only latent "
|
||||
"normalization to counter overbaking / oversaturation / colour drift at ~zero overhead. "
|
||||
"Clean 'ddim look' and clean audio (audio rides plain euler); normalization and the "
|
||||
"optional velocity bias + reactive rescue are confined to the video stream. "
|
||||
"Built for distilled few-step LTXAV at CFG=1."
|
||||
)
|
||||
|
||||
def get_sampler(self, normalize_strength=0.5, normalize_start_sigma=0.9,
|
||||
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,
|
||||
rescue_prompt_sig=None, sigmas=None):
|
||||
prepared_sigmas = sigmas.detach().clone() if isinstance(sigmas, torch.Tensor) else sigmas
|
||||
sampler = comfy.samplers.KSAMPLER(
|
||||
sample_funpack_normalizing,
|
||||
extra_options={
|
||||
"normalize_strength": normalize_strength,
|
||||
"normalize_start_sigma": normalize_start_sigma,
|
||||
"velocity_bias_mode": velocity_bias_mode,
|
||||
"velocity_bias_strength": velocity_bias_strength,
|
||||
"velocity_bias_source": velocity_bias_source,
|
||||
"velocity_refinement_key": velocity_refinement_key,
|
||||
"rescue_mode": rescue_mode,
|
||||
"rescue_threshold": rescue_threshold,
|
||||
"rescue_strength": rescue_strength,
|
||||
"rescue_prompt_sig": rescue_prompt_sig,
|
||||
}
|
||||
)
|
||||
return (sampler, prepared_sigmas)
|
||||
|
||||
|
||||
class FunPackLTXAVSceneChainSampler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
|
||||
+45
-3
@@ -12,7 +12,7 @@ const TEMPORAL_STYLES = ["natural", "accelerate", "decelerate", "loop", "freeze"
|
||||
const SB_MODES = ["Pass-through", "Manual", "Auto", "Learning"];
|
||||
const CATEGORY_ORDER = ["action", "camera", "subject", "appearance", "environment", "style", "quality", "details"];
|
||||
const TABS = ["Session", "Scene", "Shortcuts", "Transitions", "Refiner", "Advisor", "LoRA", "Sampler", "Adjustments", "Timeline"];
|
||||
const SAMPLER_TYPES = ["Hybrid Euler 2S", "Distilled Flow", "KSampler"];
|
||||
const SAMPLER_TYPES = ["Hybrid Euler 2S", "Distilled Flow", "Normalizing", "KSampler"];
|
||||
const MOTION_PULSE_MODES = ["off", "balanced", "aggressive", "custom"];
|
||||
const VELOCITY_BIAS_MODES = ["off", "capture", "apply", "capture_and_apply"];
|
||||
const KSAMPLER_NAMES = ["euler", "euler_ancestral", "dpm_2", "dpm_2_ancestral", "dpmpp_2m", "dpmpp_sde", "ddim", "uni_pc"];
|
||||
@@ -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, ab2_ramp: false, 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, ab2_ramp: false, 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" },
|
||||
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, ab2_ramp: false, 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 }, normalizing: { normalize_strength: 0.5, normalize_start_sigma: 0.9, 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, ab2_ramp: false, 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 }, normalizing: { normalize_strength: 0.5, normalize_start_sigma: 0.9, 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" },
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1568,6 +1568,48 @@ function openPanel(node) {
|
||||
dRnote.textContent = "Rating-gated: learns automatically from your ratings while on (good = steer toward, Awful = steer away). No-op until a few gens for this prompt are rated. Session reset clears it.";
|
||||
body.append(dRnote);
|
||||
}
|
||||
} else if (cfg.type === "Normalizing") {
|
||||
const nc = cfg.normalizing || (cfg.normalizing = { normalize_strength: 0.5, normalize_start_sigma: 0.9, 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 });
|
||||
body.append(sectionTitle("Normalizing settings"));
|
||||
body.append(el("div", "funpack-studio-hint", "Deterministic euler (clean 'ddim look' + clean audio) with video-only latent normalization to counter overbaking / oversaturation / colour drift at ~zero overhead. Audio is never touched. Built for distilled LTXAV at CFG=1."));
|
||||
const nStr = numInput(nc.normalize_strength, 0, 1, 0.05);
|
||||
nStr.addEventListener("input", () => { nc.normalize_strength = parseFloat(nStr.value); });
|
||||
body.append(el("div", "funpack-studio-hint", "How hard to pull the video latent's spread back when it inflates (overbaking). 0 = plain euler. 0.5 = gentle. 1.0 = clamp to reference."));
|
||||
body.append(row("normalize strength", nStr));
|
||||
const nStart = numInput(nc.normalize_start_sigma, 0, 1, 0.025);
|
||||
nStart.addEventListener("input", () => { nc.normalize_start_sigma = parseFloat(nStart.value); });
|
||||
body.append(el("div", "funpack-studio-hint", "Sigma at/below which normalization activates and anchors its reference. Above this (pure noise) it's skipped. ~0.9 = the structure-forming step."));
|
||||
body.append(row("normalize start sigma", nStart));
|
||||
const nVbm = selectEl(VELOCITY_BIAS_MODES, nc.velocity_bias_mode || "off");
|
||||
nVbm.addEventListener("change", () => { nc.velocity_bias_mode = nVbm.value; renderSampler(); });
|
||||
body.append(row("velocity bias mode", nVbm));
|
||||
if (nc.velocity_bias_mode && nc.velocity_bias_mode !== "off") {
|
||||
const nVbs = numInput(nc.velocity_bias_strength, 0, 3.0, 0.05);
|
||||
nVbs.addEventListener("input", () => { nc.velocity_bias_strength = parseFloat(nVbs.value); });
|
||||
body.append(row("velocity bias strength", nVbs));
|
||||
const nVbsrc = selectEl(["mean", "nearest"], nc.velocity_bias_source || "mean");
|
||||
nVbsrc.addEventListener("change", () => { nc.velocity_bias_source = nVbsrc.value; });
|
||||
body.append(row("bias source", nVbsrc));
|
||||
const nVrk = textInput(nc.velocity_refinement_key, "default");
|
||||
nVrk.addEventListener("input", () => { nc.velocity_refinement_key = nVrk.value; });
|
||||
body.append(row("velocity key", nVrk));
|
||||
}
|
||||
const nRsm = selectEl(["off", "on"], nc.rescue_mode ? "on" : "off");
|
||||
nRsm.addEventListener("change", () => { nc.rescue_mode = (nRsm.value === "on"); renderSampler(); });
|
||||
body.append(row("rescue mode", nRsm));
|
||||
if (nc.rescue_mode) {
|
||||
const nRst = numInput(nc.rescue_threshold, 0, 1, 0.01);
|
||||
nRst.addEventListener("input", () => { nc.rescue_threshold = parseFloat(nRst.value); });
|
||||
body.append(row("rescue threshold", nRst));
|
||||
const nRss = numInput(nc.rescue_strength, 0, 0.5, 0.01);
|
||||
nRss.addEventListener("input", () => { nc.rescue_strength = parseFloat(nRss.value); });
|
||||
body.append(row("rescue strength", nRss));
|
||||
if (!nc.velocity_bias_mode || nc.velocity_bias_mode === "off") {
|
||||
const nRsrc = selectEl(["mean", "nearest"], nc.velocity_bias_source || "mean");
|
||||
nRsrc.addEventListener("change", () => { nc.velocity_bias_source = nRsrc.value; });
|
||||
body.append(row("bias source", nRsrc));
|
||||
}
|
||||
}
|
||||
} else if (cfg.type === "KSampler") {
|
||||
body.append(sectionTitle("KSampler settings"));
|
||||
const ksSelect = selectEl(KSAMPLER_NAMES, cfg.ksampler_name || "euler");
|
||||
|
||||
Reference in New Issue
Block a user