Skip to content

fix(ddpm): use _execution_device, validate inputs, free hooks (#13649)#13671

Open
Anai-Guo wants to merge 1 commit intohuggingface:mainfrom
Anai-Guo:fix/ddpm-pipeline-execution-device-and-validation
Open

fix(ddpm): use _execution_device, validate inputs, free hooks (#13649)#13671
Anai-Guo wants to merge 1 commit intohuggingface:mainfrom
Anai-Guo:fix/ddpm-pipeline-execution-device-and-validation

Conversation

@Anai-Guo
Copy link
Copy Markdown

@Anai-Guo Anai-Guo commented May 1, 2026

What does this PR do?

Fixes the three issues called out in the ddpm model/pipeline review (cc @hlky). All three changes live in pipelines/ddpm/pipeline_ddpm.py and follow the suggested fixes from the issue, with precedents from DDIMPipeline and ConsistencyModelPipeline.

Issue 1 — DDPMPipeline does not run latents on the offload execution device

DDPMPipeline declares model_cpu_offload_seq = "unet" but initializes the latent on self.device (which stays CPU under enable_model_cpu_offload()) and never calls self.maybe_free_model_hooks() before returning. Switched to self._execution_device, threaded that into the randn_tensor call (and the mps branch), and added the maybe_free_model_hooks() call before the return so the offload contract is honored and the UNet doesn't stay resident on the accelerator.

Issue 2 — Generator lists are not validated against batch_size

A short generator list either gets silently treated like a single generator or raises a raw IndexError. Added the same ValueError guard DDIMPipeline and ConsistencyModelPipeline use, so users get a clear message when len(generator) != batch_size.

Issue 3 — Invalid output_type values silently return NumPy

Any output_type other than "pil" previously fell through to NumPy, including typos and "pt". Added an explicit output_type in {"pt", "np", "pil"} check at the top of __call__, made "pt" actually return the tensor (skipping the .cpu().numpy() round-trip), and updated the docstring to mention torch.Tensor. Behavior for the documented values ("pil", default; "np") is unchanged.

Reproductions

The repros from #13649 are short and self-contained — see the issue body for runnable scripts that demonstrate each failure mode.

Before submitting

  • Did you read the contributor guideline?
  • Did you read our philosophy doc (important for complex PRs)?
  • Was this discussed/approved via a GitHub issue or the forum? Please add a link to it if that's the case. Yes — ddpm model/pipeline review #13649.
  • Did you make sure to update the documentation with your changes? Doc string updated in this file; no separate docs page touched.
  • Did you write any new necessary tests?

Who can review?

@hlky — this addresses all three issues from the ddpm review.

🤖 Generated with Claude Code

…gface#13649)

Issue 1: replace self.device with self._execution_device so model_cpu_offload's
execution device is honored, and call self.maybe_free_model_hooks() before return
to satisfy the offload contract.

Issue 2: validate that len(generator) == batch_size for list generators, raising
ValueError instead of silently mishandling per-sample seeding (matches DDIM/
ConsistencyModel pipelines).

Issue 3: validate output_type and add 'pt' tensor output. Previously any value
other than 'pil' silently fell through to NumPy.

Closes huggingface#13649.
@github-actions github-actions Bot added pipelines size/S PR with diff < 50 LOC labels May 1, 2026
Copy link
Copy Markdown
Contributor

@hlky hlky left a comment

Choose a reason for hiding this comment

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

Thanks @Anai-Guo.

I left a few comments. Some are just confirming that the PR resolves the reported issues, and a couple are broader questions for maintainers about whether these patterns should become review/agent rules:

  • validating output_type
  • preferring VaeImageProcessor.postprocess(...) over manual clamp/permute/NumPy/PIL handling

Overall, this looks like it addresses the ddpm findings from #13649.

Also, feel free to tag me on PRs that come from the review issues / meta issue since I can usually confirm whether they address the reported finding. For approval/merge, please also tag an appropriate maintainer since I can review the issue context but cannot approve. In general, I’m happy to help clarify the reports or point out related patterns from #13656 if useful.

Comment on lines +101 to +102
if output_type not in ["pt", "np", "pil"]:
raise ValueError(f"output_type must be one of ['pt', 'np', 'pil'], got '{output_type}'.")
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.

cc @yiyixuxu Should output_type validation be a review rule?

Comment on lines +104 to +108
if isinstance(generator, list) and len(generator) != batch_size:
raise ValueError(
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
)
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.

Great, this resolves issue 2

Comment on lines 110 to +128
@@ -108,12 +120,12 @@ def __call__(
else:
image_shape = (batch_size, self.unet.config.in_channels, *self.unet.config.sample_size)

if self.device.type == "mps":
if device.type == "mps":
# randn does not work reproducibly on mps
image = randn_tensor(image_shape, generator=generator, dtype=self.unet.dtype)
image = image.to(self.device)
image = image.to(device)
else:
image = randn_tensor(image_shape, generator=generator, device=self.device, dtype=self.unet.dtype)
image = randn_tensor(image_shape, generator=generator, device=device, dtype=self.unet.dtype)
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.

Great, this + a later line resolves issue 1

Comment on lines 143 to +147
image = (image / 2 + 0.5).clamp(0, 1)
image = image.cpu().permute(0, 2, 3, 1).numpy()
if output_type == "pil":
image = self.numpy_to_pil(image)
if output_type != "pt":
image = image.cpu().permute(0, 2, 3, 1).numpy()
if output_type == "pil":
image = self.numpy_to_pil(image)
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.

As per additional review in #13663 (comment) this could use VaeImageProcessor, this would fix all output_type's without the if statements. cc @yiyixuxu For awareness, this is another case that supports introducing VaeImageProcessor usage as a review rule.

@Anai-Guo
Copy link
Copy Markdown
Author

Anai-Guo commented May 1, 2026

Thanks @hlky for the review! Glad the fix resolves both reported issues.

For the line 147 suggestion — happy to refactor to VaeImageProcessor.postprocess(...) in this PR if @yiyixuxu agrees that's the preferred direction; otherwise I can keep this PR scoped to the original two bugs and leave the broader VaeImageProcessor migration for a follow-up. Just let me know which you prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pipelines size/S PR with diff < 50 LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants