Skip to content

Fix division-by-zero NaN in shift_terminal with a single denoising step - #14415

Open
mayuriphad wants to merge 1 commit into
huggingface:mainfrom
mayuriphad:fix/issue-14411-shift-terminal-single-step
Open

Fix division-by-zero NaN in shift_terminal with a single denoising step#14415
mayuriphad wants to merge 1 commit into
huggingface:mainfrom
mayuriphad:fix/issue-14411-shift-terminal-single-step

Conversation

@mayuriphad

Copy link
Copy Markdown

Fixes #14411

Root cause

stretch_shift_to_terminal() rescales the sigma schedule so it terminates at config.shift_terminal, computing scale_factor = one_minus_z[-1] / (1 - shift_terminal). When num_inference_steps=1, the single sigma is always 1.0, so one_minus_z[-1] == 0, making scale_factor == 0 and the division produce NaN. That NaN sigma then crashes index_for_timestep() with an IndexError inside scheduler.step().

Fix

There is nothing to stretch with only one step, so skip the call to stretch_shift_to_terminal() when len(sigmas) <= 1. The same if self.config.shift_terminal: pattern (guarding a call to stretch_shift_to_terminal) exists in three schedulers that support shift_terminal, so the guard is applied consistently to all three:

  • FlowMatchEulerDiscreteScheduler
  • FlowMatchLCMScheduler
  • UniPCMultistepScheduler (flow-sigmas path)

None of the touched lines are inside # Copied from blocks, so no make fix-copies follow-up is needed.

Verification

  • Reproduced the exact NaN via the original (unguarded) logic.
  • Ran FlowMatchEulerDiscreteScheduler.set_timesteps(num_inference_steps=1, ...) with shift_terminal=0.1 against the patched code: sigmas are [1., 0.], no NaN.
  • Confirmed num_inference_steps=4 is unaffected and still correctly terminates at shift_terminal=0.1 ([1.0, 0.7, 0.4, 0.1, 0.0]).

stretch_shift_to_terminal() rescales sigmas so the schedule ends at
config.shift_terminal, using scale_factor = one_minus_z[-1] / (1 - shift_terminal).
With num_inference_steps=1 the only sigma is always 1.0, so one_minus_z[-1] is 0,
scale_factor is 0, and the division produces NaN. That NaN sigma then breaks
index_for_timestep() with an IndexError during scheduler.step().

There is nothing to stretch with a single step, so skip the call when
len(sigmas) <= 1. Applied the same guard to the three schedulers that support
shift_terminal: FlowMatchEulerDiscreteScheduler, FlowMatchLCMScheduler, and
UniPCMultistepScheduler (flow-sigmas path).

Fixes huggingface#14411

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 7, 2026 09:08
@github-actions github-actions Bot added schedulers fixes-issue size/S PR with diff < 50 LOC labels Aug 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a division-by-zero NaN in stretch_shift_to_terminal() when num_inference_steps=1 and shift_terminal is enabled, by skipping the terminal-stretch rescaling when there鈥檚 only a single sigma value. This prevents NaN sigmas from propagating into index_for_timestep() / scheduler.step().

Changes:

  • Guard stretch_shift_to_terminal() application with len(sigmas) > 1 in all schedulers that support shift_terminal.
  • Add/adjust inline rationale comments explaining why the single-step case is skipped (FlowMatch schedulers).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/diffusers/schedulers/scheduling_flow_match_euler_discrete.py Skip terminal stretch when only one sigma to prevent NaN/div-by-zero in single-step inference.
src/diffusers/schedulers/scheduling_flow_match_lcm.py Apply the same single-step guard for shift_terminal stretching (LCM variant).
src/diffusers/schedulers/scheduling_unipc_multistep.py Apply the single-step guard on the UniPC flow-sigmas path to avoid the same NaN edge case.
Suppressed comments (1)

src/diffusers/schedulers/scheduling_flow_match_euler_discrete.py:355

  • The explanatory comment here is slightly too absolute: set_timesteps allows custom sigmas/timesteps, so with num_inference_steps=1 the single sigma is not necessarily 1.0. The division-by-zero rationale applies to the default schedule (and typical usage), so it would be clearer to qualify the statement to avoid misleading future readers.
        # 3. If required, stretch the sigmas schedule to terminate at the configured `shift_terminal` value. This is
        #    skipped when there is only a single step, since there is nothing to stretch and the terminal rescaling
        #    otherwise divides by zero (the single sigma is always 1.0, i.e. `one_minus_z[-1]` is always 0).

馃挕 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +353 to 357
# 3. If required, stretch the sigmas schedule to terminate at the configured `shift_terminal` value. This is
# skipped when there is only a single step, since there is nothing to stretch and the terminal rescaling
# otherwise divides by zero (the single sigma is always 1.0, i.e. `one_minus_z[-1]` is always 0).
if self.config.shift_terminal and len(sigmas) > 1:
sigmas = self.stretch_shift_to_terminal(sigmas)
Comment on lines +362 to +364
# 3. If required, stretch the sigmas schedule to terminate at the configured `shift_terminal` value. This is
# skipped when there is only a single step, since there is nothing to stretch and the terminal rescaling
# otherwise divides by zero (the single sigma is always 1.0, i.e. `one_minus_z[-1]` is always 0).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Division by 0 bug when only one denoising step

3 participants