-
Notifications
You must be signed in to change notification settings - Fork 6.9k
LTXEulerAncestralRFScheduler.set_timesteps(sigmas=...) does not validate monotonicity, causing silent incorrect denoising #13411
Description
Describe the bug
LTXEulerAncestralRFScheduler.set_timesteps accepts an externally-supplied sigmas argument without validating that the schedule is monotonically non-increasing. When step() is called on a non-monotone schedule, the ancestral RF decomposition computes sigma_down outside [0, 1] and alpha_down < 0, which violates the CONST parametrization invariant. The output is numerically finite — no exception, no NaN, no warning — but denoising does not occur. The corruption is silent.
From PHILOSOPHY.md, line 32:
"Raising concise error messages is preferred to silently correct erroneous input. Diffusers aims at teaching the user, rather than making the library as easy to use as possible."
Reproduction
import torch
from diffusers import LTXEulerAncestralRFScheduler
scheduler = LTXEulerAncestralRFScheduler()
# Non-monotone schedule: sigma increases at step 0 → 1.
# A misconfigured ComfyUI workflow can emit exactly this kind of list.
scheduler.set_timesteps(sigmas=[0.2, 0.8, 0.5, 0.0]) # should raise — does not
torch.manual_seed(0)
sample = torch.randn(1, 4, 8, 8)
model_output = torch.zeros_like(sample) # model predicts zero signal
out = scheduler.step(model_output, scheduler.timesteps[0], sample)
print(f"Input norm : {sample.norm():.4f}")
print(f"Output norm : {out.prev_sample.norm():.4f}")
print(f"Ratio : {(out.prev_sample.norm() / sample.norm()).item():.3f}x")
# Expected: small positive ratio converging toward sigma_next/sigma ≈ 0.25
# Actual: ratio ≈ 1.45, sign-inverted — latent is corrupted, not denoisedExpected behaviour
set_timesteps raises ValueError immediately when sigmas is not monotonically non-increasing, before any internal state is mutated.
Actual behaviour — full numerical trace
With sigma = 0.2, sigma_next = 0.8, eta = 1.0 (from step() in scheduling_ltx_euler_ancestral_rf.py):
downstep_ratio = 1.0 + (sigma_next / sigma - 1.0) * eta
= 1.0 + (0.8 / 0.2 - 1.0) * 1.0
= 4.0 ← should be in (0, 1] for a valid denoising step
sigma_down = sigma_next * downstep_ratio
= 0.8 * 4.0 = 3.2 ← outside [0, 1]; CONST parametrization undefined
sigma_ratio = sigma_down / sigma = 3.2 / 0.2 = 16.0
x_euler = 16.0 * sample + (−15.0) * denoised ← 16× amplification
alpha_down = 1 − sigma_down = 1 − 3.2 = −2.2 ← negative; undefined in CONST space
scale = alpha_ip1 / alpha_down = 0.2 / −2.2 = −0.091 ← sign flip
# renoise_coeff = sqrt(clamp(sigma_next² − sigma_down² · alpha_ip1² / alpha_down², min=0))
renoise_coeff ≈ 0.745
prev_sample ≈ −0.091 × x_euler + 0.745 × noise
≈ −1.45 × sample + 1.36 × denoised + 0.745 × noise
The sample coefficient is −1.45 (sign-inverted, slightly amplified) rather than a small positive value. The output is finite, plausibly shaped, and completely wrong.
Why >= not >
The check should use sigmas[:-1] >= sigmas[1:] (non-strict). Strict monotonicity would reject plateau steps — consecutive equal sigmas that are intentional in img2img partial schedules using set_begin_index. Non-strict preserves that use case while catching every reversed or partially-reversed schedule.
Proposed fix
# set_timesteps(), after the ndim guard, before the terminal-sigma warning:
if len(sigmas_tensor) > 1 and not (sigmas_tensor[:-1] >= sigmas_tensor[1:]).all():
sig_list = sigmas_tensor.tolist()
sig_repr = str(sig_list) if len(sig_list) <= 8 else f"{sig_list[:4]} ... {sig_list[-4:]} (len={len(sig_list)})"
raise ValueError(
f"`sigmas` must be monotonically non-increasing (each entry >= the next), got {sig_repr}"
)A companion range check is also needed — the CONST parametrization requires σ ∈ [0, 1], which set_timesteps does not currently enforce.
Note: FlowMatchEulerDiscreteScheduler is not affected — it generates its sigma schedule internally and does not expose a raw sigmas= argument. The blast radius is asymmetric.
Additional context
There is currently no test file for LTXEulerAncestralRFScheduler in tests/schedulers/, and the scheduler is absent from the API reference _toctree.yml despite being publicly exported.
I have a PR ready that addresses all of the above: monotonicity and range checks, eta boundary validation, 16 unit tests, an API docs page, and the toctree entry. Happy to open it once this issue is acknowledged.
/cc @yiyixuxu