Build the strided timestep subsequence a DDIM sampler walks when it generates an image in 50 steps from a model trained with 1000, together with the αˉ lookup that handles the final step.
Because the DDIM update only ever references αˉ at the current and target timesteps, nothing forces the sampler to visit every trained timestep. It can walk any decreasing subsequence τS>τS−1>⋯>τ1, which is why DDIM turns 1000 network evaluations into 20-50 with little quality loss.
The standard uniform stride, as implemented in diffusers, is:
That final convention is final_alpha_cumprod = 1.0 in the reference code, and it is what lets the last step land on x^0 exactly.
Implement two functions:
def ddim_timesteps(T, num_steps):
def alpha_bar_at(alpha_bars, t):
ddim_timesteps returns a list of (t, t_prev) tuples of Python ints in descending sampling order, with t_prev set to -1 whenever the strided target falls below zero. alpha_bar_at returns float(alpha_bars[t]), or 1.0 when t < 0.
ddim_timesteps returns a list of 2-tuples of ints; alpha_bar_at returns a float.
print(ddim_timesteps(1000, 5))
The stride is 200, so the ascending grid is [0, 200, 400, 600, 800]; reversed and paired with t - 200 this gives [(800, 600), (600, 400), (400, 200), (200, 0), (0, -1)].
print(ddim_timesteps(1000, 5))
[(800, 600), (600, 400), (400, 200), (200, 0), (0, -1)]
step = 1000 // 5 = 200, so the ascending grid is [0, 200, 400, 600, 800]. Reversed, sampling walks 800, 600, 400, 200, 0, and each target is 200 lower. The last target would be -200, which is below zero, so it is reported as -1 -- the signal that alpha_bar_prev is 1 and this step lands on clean data.
T need not be divisible by num_steps.num_steps timesteps -- np.arange(0, T, step) can return one extra when the division is inexact.ints inside the tuples, not NumPy scalars (the tuples are printed directly).t_prev is -1, never a smaller negative number.alpha_bar_at must return 1.0 for any negative t, and never index with a negative number.