Skip to content

Add exponential Euler, stochastic exponential Euler, DPM-Solver++(2M), and UniC-2 diffusion samplers - #1932

Open
CharlelieLrt wants to merge 25 commits into
NVIDIA:mainfrom
CharlelieLrt:new-diffusion-samplers
Open

CharlelieLrt wants to merge 25 commits into
NVIDIA:mainfrom
CharlelieLrt:new-diffusion-samplers

Conversation

@CharlelieLrt

@CharlelieLrt CharlelieLrt commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

PhysicsNeMo Pull Request

Description

This PR adds four composable diffusion solvers to physicsnemo.diffusion.samplers. Each solver can be instantiated directly or selected by passing its string key to sample().

Solvers

  • ExponentialEulerSolver ("exponential_euler") is a first-order solver for semi-linear ODEs. It supports DDIM-like sampling and distilled few-step models.
  • EDMStochasticExponentialEulerSolver ("edm_stochastic_exponential_euler") adds EDM-style churn and controllable re-noising to exponential Euler. It supports stochastic, ancestral, and full re-noising configurations for distilled and consistency models.
  • DPMPlusPlus2M ("dpmpp_2m") is a configurable second-order multistep solver with one denoiser evaluation per step. Its defaults recover classical two-step Adams-Bashforth; configuring it with the schedule's semi-linear structure recovers the published DPM-Solver++(2M) formulation.
  • DPMPlusPlus2MUniC2 ("dpmpp_2m_unic2") adds a UniC-2 corrector stage on top of DPMPlusPlus2M, raising the update to third order while keeping one denoiser evaluation per step, at the cost of a slightly larger memory footprint.

API integration

  • Adds LinearGaussianNoiseScheduler.get_linear_denoiser() to expose the semi-linear structure of the ODE/SDE right-hand side that the new exponential and multistep solvers rely on. It complements get_denoiser(), which returns the full right-hand side.
  • Adds LinearGaussianNoiseScheduler.snr() to expose the schedule's signal-to-noise ratio, used as the multistep extrapolation coordinate by DPMPlusPlus2M and DPMPlusPlus2MUniC2.
  • Extends sample() with string-key selection and solver-specific options for all four solvers.
  • Documents the new solvers, their intended use cases, and their integration with linear-Gaussian noise schedules.

Relationship to #1896

This PR supersedes #1896. It builds on the DPM-Solver++(2M) implementation from that PR through a leaner and more compositional API. See #1896 for quantitative evidence of the sampler's benefits.

Checklist

Dependencies

None.

Review Process

All PRs are reviewed by the PhysicsNeMo team before merging.

Depending on which files are changed, GitHub may automatically assign a maintainer for review.

We are also testing AI-based code review tools (e.g., Greptile), which may add automated comments with a confidence score.
This score reflects the AI’s assessment of merge readiness and is not a qualitative judgment of your work, nor is
it an indication that the PR will be accepted / rejected.

AI-generated feedback should be reviewed critically for usefulness.
You are not required to respond to every AI comment, but they are intended to help both authors and reviewers.
Please react to Greptile comments with 👍 or 👎 to provide feedback on their accuracy.

…on solvers. Configurations reproduce DDIM, the re-noising sampler of distilled models, and DPM-Solver++(2M).

Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
… ExponentialAB2Solver with ExponentialEulerSolver, EDMStochasticExponentialEulerSolver, and a dedicated DPMPlusPlus2M.

Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review (133 files, 100 file limit).

Bypass the limit by tagging @greptile-apps to review.

@github-actions

Copy link
Copy Markdown
Contributor

CODEOWNERS review map

Current for commit 87ae40046642. An approval covers every file listed for that owner; one owner is sufficient for shared files.

@CharlelieLrt — 133 file(s)
  • docs/api/diffusion/samplers.rst
  • physicsnemo/diffusion/noise_schedulers/linear_gaussian.py
  • physicsnemo/diffusion/samplers/init.py
  • physicsnemo/diffusion/samplers/dpmpp_2m.py
  • physicsnemo/diffusion/samplers/edm_stochastic_exponential_euler.py
  • physicsnemo/diffusion/samplers/exponential_euler.py
  • physicsnemo/diffusion/samplers/samplers.py
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_1d_epsilonpred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_1d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_1d_scorepred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_1d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_1d_x0pred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_1d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_2d_epsilonpred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_2d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_2d_scorepred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_2d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_2d_x0pred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_2d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_3d_epsilonpred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_3d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_3d_scorepred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_3d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_3d_x0pred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_edm_3d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_1d_epsilonpred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_1d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_1d_scorepred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_1d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_1d_x0pred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_1d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_2d_epsilonpred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_2d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_2d_scorepred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_2d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_2d_x0pred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_2d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_3d_epsilonpred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_3d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_3d_scorepred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_3d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_3d_x0pred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_ve_3d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_1d_epsilonpred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_1d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_1d_scorepred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_1d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_1d_x0pred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_1d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_2d_epsilonpred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_2d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_2d_scorepred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_2d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_2d_x0pred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_2d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_3d_epsilonpred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_3d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_3d_scorepred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_3d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_3d_x0pred.pth
  • test/diffusion/data/test_samplers_dpmpp_2m_vp_3d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_1d_epsilonpred.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_1d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_1d_scorepred.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_1d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_1d_x0pred.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_1d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_2d_epsilonpred.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_2d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_2d_scorepred.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_2d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_2d_x0pred.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_2d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_3d_epsilonpred.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_3d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_3d_scorepred.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_3d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_3d_x0pred.pth
  • test/diffusion/data/test_samplers_exponential_euler_edm_3d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_1d_epsilonpred.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_1d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_1d_scorepred.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_1d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_1d_x0pred.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_1d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_2d_epsilonpred.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_2d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_2d_scorepred.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_2d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_2d_x0pred.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_2d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_3d_epsilonpred.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_3d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_3d_scorepred.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_3d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_3d_x0pred.pth
  • test/diffusion/data/test_samplers_exponential_euler_ve_3d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_1d_epsilonpred.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_1d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_1d_scorepred.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_1d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_1d_x0pred.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_1d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_2d_epsilonpred.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_2d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_2d_scorepred.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_2d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_2d_x0pred.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_2d_x0pred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_3d_epsilonpred.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_3d_epsilonpred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_3d_scorepred.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_3d_scorepred_teval.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_3d_x0pred.pth
  • test/diffusion/data/test_samplers_exponential_euler_vp_3d_x0pred_teval.pth
  • test/diffusion/data/test_solvers_dpmpp_2m_1d_step.pth
  • test/diffusion/data/test_solvers_dpmpp_2m_2d_step.pth
  • test/diffusion/data/test_solvers_dpmpp_2m_3d_step.pth
  • test/diffusion/data/test_solvers_exponential_euler_1d_step.pth
  • test/diffusion/data/test_solvers_exponential_euler_2d_step.pth
  • test/diffusion/data/test_solvers_exponential_euler_3d_step.pth
  • test/diffusion/data/test_solvers_stoch_exp_euler_churn_1d_step.pth
  • test/diffusion/data/test_solvers_stoch_exp_euler_churn_2d_step.pth
  • test/diffusion/data/test_solvers_stoch_exp_euler_churn_3d_step.pth
  • test/diffusion/data/test_solvers_stoch_exp_euler_nochurn_1d_step.pth
  • test/diffusion/data/test_solvers_stoch_exp_euler_nochurn_2d_step.pth
  • test/diffusion/data/test_solvers_stoch_exp_euler_nochurn_3d_step.pth
  • test/diffusion/data/test_solvers_stoch_exp_euler_renoise_1d_step.pth
  • test/diffusion/data/test_solvers_stoch_exp_euler_renoise_2d_step.pth
  • test/diffusion/data/test_solvers_stoch_exp_euler_renoise_3d_step.pth
  • test/diffusion/test_noise_schedulers.py
  • test/diffusion/test_samplers.py
  • test/diffusion/test_solvers.py
@megnvidia — 1 file(s)
  • docs/api/diffusion/samplers.rst

Comment /codeowners-info to refresh.

@CharlelieLrt CharlelieLrt self-assigned this Aug 18, 2026
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
@gertln

gertln commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Thanks for integrating the DPM++2M work into this PR.

I tested the public sample(..., solver="dpmpp_2m") path at commit 7f40b6a.
Its implementation changed substantially in commit 35c7a1d8, so I want to
confirm which numerical method the "dpmpp_2m" key is intended to run.

87ae4004 introduced DPMPlusPlus2M using λ = log(α/σ) as its integration
variable. It used alpha_fn and sigma_fn, and handled the zero-noise endpoint
separately because λ diverges there. 35c7a1d8 replaced that implementation
with one that advances directly in the scheduler's time variable and accepts
one optional linear_fn. The file no longer references the log-SNR clock.

The default path. The class docstring says that omitting linear_fn gives
the "classical two-step Adams-Bashforth method", so that class behavior appears
intentional. What I could not tell is whether the same is intended for the
"dpmpp_2m" string key passed to sample(). Because linear_fn is optional,
sample(..., solver="dpmpp_2m") with no solver_options constructs the solver
with linear_fn=None and therefore selects that method.
I confirmed that its output matches an independent variable-step AB2
implementation to at most 4.4e-16, both with explicit Karras time steps and
with scheduler-generated ones. However, the sample() docstring and
docs/api/diffusion/samplers.rst describe the key as DPM-Solver++(2M) without
mentioning linear_fn.

On the analytic EDM problem in the attached reproduction, the public path has
relative error 2.39e-1 compared with 1.57e-1 for DPM++2M at 8 NFE, and 7.67e-3
compared with 3.69e-3 at 64 NFE. Both methods are second order, but their errors
at the same step count differ.

fig_pr1932_single

Standard DPM++2M is still available, which I want to be clear about. If I
pass the λ ladder as time_steps, use a denoiser that accepts λ as its time
input, and set solver_options={"linear_fn": lambda t: -ones}, then sample()
matches a direct implementation of the paper to at most 4.5e-15.

This recipe is not documented, and its time steps do not follow the documented
ordering. The λ values increase, while time_steps are documented as
decreasing. The reproduction stops at σ_min, so its final λ is finite.
Reaching zero noise needs a final λ = +inf rather than the documented
t_0 = 0. That is accepted, and looks correct — the isfinite guard drops
the step to first order and it returns the data prediction exactly — but is
passing +inf the intended way to take the terminal step under this recipe?

The scheduler-supplied linear_fn. The class Examples block uses
linear_fn = get_linear_denoiser(prediction_type="x0"). For EDM, this gives
A(t) = 1/t. The solver holds this value fixed over each step, so
exp(h·A(t_cur)) is only an approximation to the linear update. The example
also steps to t = 0.0, where z = −1. Its final step therefore returns
e⁻¹·x + (1 − e⁻¹)·D instead of the data prediction D. Running the example
with x0_pred = lambda x, t: x * 0.1 gives x_0 = 0.566086, while the data
prediction is 0.129176. On a ladder ending at zero the error also falls far
more slowly than second order: 5.5e0 at 8 steps, 9.6e-1 at 128, and still
3.6e-1 at 2048.

This is also the configuration used by the solver test matrix
(test_solvers.py:117). For score or epsilon prediction on EDM, A = 0, so
this issue is specific to x0 prediction.

Test coverage. As a check, I replaced the DPM++ multistep coefficient
h_phi1/2 · r with the plain AB2 coefficient h/2 · r. All 300 collected
dpmpp_2m tests still passed. The dpmpp_2m reference files are not in the
tree, so load_or_create_reference generates them on the first run rather than
comparing against a stored baseline.

Could you clarify:

  1. Was the switch to the scheduler's time variable in 35c7a1d8 intended to
    change what dpmpp_2m selects, or was the λ-based update meant to remain?
  2. If callers are expected to provide the λ transformation themselves, would
    it be worth documenting the recipe and explaining its different time-step
    ordering?
  3. Is the A(t) = 1/t pairing shown in Examples intended for ladders that
    terminate at zero noise, given timesteps() always appends 0?

A short CPU reproduction pinned to 7f40b6a is below. It uses sample() and
solver_options throughout, although the λ ladder ascends rather than
decreases, unlike the documented time_steps behavior.

repro_pr1932_public.py

…ring the existing get_denoiser coverage. Also check in the not-yet-committed golden files for the get_linear_denoiser and DPMPlusPlus2M test suites.

Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
…the solver and sampler suites. Also unify the DPMPlusPlus2M step into a single masked update path, so a compiled solver needs no caller-side priming.

Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
@CharlelieLrt

Copy link
Copy Markdown
Collaborator Author

@gertln

I reintroduced the extrapolation variable lambda as an additional callback passed to the solver. To reproduce DPM-Solver++(2M), lambda needs to be set to the log-SNR of the associated noise schedule. This is shown in the docstring example.

The time steps grid does not need to be in lambda coordinates. The solver always operates in (x, t) coordinates. The lambda conversion is purely internal (only used to approximate the moments/integrals).

@CharlelieLrt

Copy link
Copy Markdown
Collaborator Author

@megnvidia Could I get your review on this PR for docs modifications? Thanks!

@gertln

gertln commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Thanks @CharlelieLrt, lambda_fn makes the multistep extrapolation use the log-SNR coordinate. I believe the documented native-time config still differs from DMP++2M in the linear update.
The first step is the simplest check because there is no history, so lambda_fn does not affect the result. The current code propagates the linear term with exp((t_next - t_cur) * A(t_cur)), while DPM++2M uses the linear coefficient integrated over the interval. For EDM that exact multiplier is sigma_next/sigma_cur.
For the first 5.0 -> 2.5 step in the documented x0 config with x=2 and x0_pred=0.1*x we have:

current solver multiplier: exp(-0.5) = 0.6065
DPM++2M multiplier: 2.5/5.0 = 0.5

current solver output: 1.291755
DPM++2M output: 1.1

To be clear the kernel can express canonical DPM2++2M but what I believe we can not reproduce is DPM++2M using the documented x0 config on the scheduler's native time grid, where A(t) varies. Is this interpretation correct or am I missing something?

@CharlelieLrt

CharlelieLrt commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

@gertln this AI slop is really not helpful. Can you please actually look at the code and let me know where you think there's an error?

@megnvidia megnvidia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@laserkelvin laserkelvin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think more generally, adding regression tests that check the solver value at t=0, t=0.5, and t=1 against simple cases would be good for these three solvers (ideally all solvers). If you can resolve that and make sure all tests pass, it'll give me some more time to review as well

Comment thread physicsnemo/diffusion/noise_schedulers/linear_gaussian.py
Comment thread physicsnemo/diffusion/samplers/exponential_euler.py
…evaluate the nonlinear weight as an exponential-kernel integral via a private Gauss-Legendre quadrature, extrapolate the predictor-like term in DPM-Solver++(2M), and add a snr method to the linear-Gaussian scheduler. Regenerate the solver and sampler golden files for the new numerics.

Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
@CharlelieLrt CharlelieLrt changed the title Add exponential Euler, stochastic exponential Euler, and DPM-Solver++(2M) diffusion samplers Add exponential Euler, stochastic exponential Euler, DPM-Solver++(2M), and UniC-2 diffusion samplers Aug 28, 2026
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>

@laserkelvin laserkelvin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ooga! Me look long at this PR. Short version: me like. Math check out — derived by hand twice (independent reviewers): DDIM reduction, DPM-Solver++(2M) update, UniC-2 corrector coefficients, EDM churn — all correct. VP sigma_to_t rewrite is a real fix (old code return slightly negative t at sigma=0). Golden files clean (334 new, all referenced, zero orphans). Compile design with masked updates + stable cache storage is smart.

Me leave comments inline. Two big asks, rest small.

BIG ASK 1 — string keys not do what name say. sample(..., solver="dpmpp_2m") with no solver_options build solver with empty pockets (no scheduler callbacks) → quietly run classical AB2, not DPM-Solver++(2M). Same story for the other three new keys. Defaults are a fine feature (me see the dpmpp_2m_ab2 test use them on purpose) — but a user who not read the class docstrings get a different method than the name promise, and nothing tell them. Ask: warn when the new keys are used without callbacks + per-key honesty in the sample() docstring and docs page. Details inline at samplers.py.

BIG ASK 2 — the new consistency test cannot see the fancy parts. TestStepConsistency use slope=0, which make the nonlinear term N≡0 — so the J quadrature, the DPM extrapolation, and the UniC-2 corrector all get multiplied by zero, and the single-step design never reach UniC-2 steady state. The "second-order" / "third-order" claims today rest only on self-generated goldens, which catch changes, not wrong-from-the-start. Ask: one empirical order-of-accuracy test — golden-free mock-up inline at test_solvers.py, ~50 lines, CPU.

Small asks inline: dedup _nonlinear_weight ×4 into _utils.py; validate denoising_type; CHANGELOG fixes; strengthen the gradient-flow assertion; one SDE end-to-end config.

One question inline (dpmpp_2m_unic2.py): please confirm grads-through-time are out of scope — the copy_ cache updates would crash backward loudly in that config; if out of scope, worth a one-line invariant comment.

And noting here because the file is not in this diff: DomainParallelNoiseScheduler forwards get_denoiser but not the new get_linear_denoiser/snr — domain-parallel users must find the .inner_scheduler trick themselves. Two small pass-through methods would keep parity.

Nice work overall — the numerics are solid; the asks above are about what users and tests can see.

Comment thread physicsnemo/diffusion/samplers/samplers.py
Comment thread test/diffusion/test_solvers.py Outdated
Comment thread physicsnemo/diffusion/samplers/exponential_euler.py Outdated
Comment thread physicsnemo/diffusion/noise_schedulers/linear_gaussian.py
Comment thread physicsnemo/diffusion/samplers/dpmpp_2m_unic2.py Outdated
Comment thread physicsnemo/diffusion/samplers/dpmpp_2m_unic2.py
Comment thread physicsnemo/diffusion/samplers/dpmpp_2m.py
Comment thread CHANGELOG.md Outdated
Comment thread test/diffusion/test_samplers.py Outdated
…ver.

Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
…ian.py

Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
…o make them differentiable wrt timesteps

Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
@CharlelieLrt

Copy link
Copy Markdown
Collaborator Author

/ok to test f64647e

Signed-off-by: Charlelie Laurent <claurent@nvidia.com>
@CharlelieLrt

Copy link
Copy Markdown
Collaborator Author

/ok to test eb84621

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants