+# diffusion-models
A from-scratch research engineering repository for diffusion generative models: mathematics, implementations, paper reproductions, benchmarks, and honest experimental results.
This repository implements diffusion generative models from first principles: every scheduler, sampler, loss function, and core network component is written from scratch in PyTorch, with a full mathematical derivation for every piece of executable code. It is built to demonstrate, and to teach, how diffusion models actually work underneath the abstractions of libraries like diffusers not to wrap those libraries.
Every number in this repository (benchmark timings, ablation results, training loss curves, figure outputs) was actually measured by running the corresponding code, not hard-coded or copied from a paper. Where paper-scale reproduction was not feasible within this repository's compute budget, that is stated explicitly rather than implied.
The forward (noising) process is a fixed Markov chain that gradually destroys signal by adding Gaussian noise:
By unrolling this recursion, we obtain the closed-form marginal:
where $\bar{\alpha}t = \prod{s=1}^{t} (1 - \beta_s)$. This allows direct sampling of any noisy version
Sampling requires reversing the forward chain:
The posterior conditioned on the clean image
with:
Substituting the model's estimate of
yields the final sampling update:
The variational lower bound on log-likelihood decomposes into per-timestep KL divergences:
Since both
Substituting the noise-prediction parameterization and simplifying yields the DDPM objective:
Ho et al. (2020) empirically found that dropping the per-timestep weights produces better sample quality, even though it no longer directly optimizes likelihood. This decoupling of "what the math says to optimize" from "what produces good samples" is one of the paper's most influential contributions.
DDIM generalizes the forward process to a family of non-Markovian processes sharing the same marginals
Setting
This is the basis for:
- Fast sampling: reduce from 1000 steps to 10-50
- DDIM inversion: invertible mapping from noise to image, enabling latent-space editing
In continuous time, the reverse process can be written as an SDE:
The probability-flow ODE removes the stochastic term while preserving the same marginals:
In the Karras et al. (2022) parameterization (noise level
where implementations/samplers/ode_samplers.py using Euler, Heun, and DPM-Solver-2.
- From-scratch implementations: UNet with self-/cross-attention, DDPM and DDIM schedulers, Euler/Heun/DPM-Solver ODE samplers, LoRA, classifier-free guidance — no
diffusersdependency in the core library code. - Full mathematical derivations, not just formulas: forward process, reverse process, ELBO decomposition, DDIM's non-Markovian construction, and training parameterizations, each with every algebraic step shown (
docs/theory/,mathematics/). - 11 paper summary folders, each following a strict, complete section structure (Summary, Motivation, Key Contributions, Architecture, Mathematics, Pseudocode, Implementation, Experiments, Results, Limitations, Future Work, BibTeX) — see
papers/. - 42 passing unit tests covering mathematical invariants (monotonicity, boundary conditions, determinism, gradient flow), not just "does it run."
- Real, reproducible experiments and benchmarks — including an honestly-reported negative ablation result (see
experiments/ablations/README.md), because a repository that only ever reports favorable results isn't demonstrating real research practice. - An original research contribution (
research/): a novel adaptive-step-size ODE sampler, tested against fixed-schedule baselines on an analytically exact benchmark (closed-form density, zero network-approximation error) with 20-seed statistics and a Wilcoxon significance test — including a real bug this testing found and fixed in the repository's own DPM-Solver implementation. Seeresearch/RESEARCH_NOTE.mdfor the full, honestly-reported result.
This repository's original research contribution asks: does runtime-adaptive step-size control improve diffusion ODE sampling?
Methodology:
- Analytically exact Gaussian mixture benchmark (8 modes, closed-form density, zero network error)
- 20 independent seeds per condition
- Comparison against fixed Euler, Heun, and DPM-Solver-2 at NFE budgets of 10, 20, 40, 60, 100
- Wilcoxon signed-rank significance test
Results:
| NFE budget | Euler | Heun | DPM-Solver-2 | Adaptive | Adaptive NFE used |
|---|---|---|---|---|---|
| 10 | 3.83 ± 0.20 | 56.30 ± 1.47 | 3.24 ± 0.17 | 4.18 ± 0.23 | 9.0 |
| 20 | 2.92 ± 0.12 | 2.76 ± 0.10 | 2.71 ± 0.10 | 2.76 ± 0.10 | 19.0 |
| 40 | 2.67 ± 0.09 | 2.56 ± 0.07 | 2.58 ± 0.08 | 2.56 ± 0.07 | 39.0 |
| 60 | 2.61 ± 0.08 | 2.54 ± 0.07 | 2.55 ± 0.07 | 2.55 ± 0.07 | 60.0 |
| 100 | 2.57 ± 0.07 | 2.53 ± 0.07 | 2.53 ± 0.07 | 2.55 ± 0.07 | 62.2 |
(Negative log-likelihood; lower is better. Reference floor: 2.532.)
Key Findings:
- Adaptive sampler works reliably at NFE ≥ 50, matching fixed samplers within 0.02 NLL
- Does not outperform the best-tuned fixed schedules at any budget
- Underutilizes budget at high NFE (stops at 62 NFE vs 100 allowed)
- Tiered strategy (Euler for <15 NFE, Heun for 15-50 NFE, adaptive for ≥50 NFE) is essential for robustness
Conclusion: The adaptive sampler removes the need for manual schedule tuning and works reliably at NFE ≥ 50, but does not provide a quality-per-NFE advantage over well-tuned fixed schedules.
If you are new to diffusion models, we recommend this reading order:
mathematics/notation/README.md— fix notation before reading anything else.docs/theory/forward_process.md— the forward (noising) process and its closed form.docs/theory/reverse_process.md— the learned reverse process and DDPM's training objective.docs/theory/elbo_derivation.md— the full variational lower bound derivation.papers/ddpm/README.md— see the theory tied back to the original paper and this repo's implementation.docs/theory/ddim_derivation.mdandpapers/ddim/README.md— fast sampling.docs/theory/parameterizations.md— epsilon/x0/v-prediction tradeoffs.papers/latent_diffusion/README.md,papers/controlnet/README.md,papers/dreambooth/README.md,papers/lora/README.md— practical extensions.papers/score_sde/README.md,papers/flow_matching/README.md,papers/rectified_flow/README.md,papers/consistency_models/README.md— the continuous-time/few-step generation frontier.notebooks/— run the code yourself.docs/interview_prep/— test your understanding.
diffusion-models/
├── README.md # this file
├── LICENSE # MIT
├── CONTRIBUTING.md, CODE_OF_CONDUCT.md, CHANGELOG.md, ROADMAP.md
├── requirements.txt, pyproject.toml, setup.py
├── docs/
│ ├── theory/ # full mathematical derivations
│ │ ├── forward_process.md
│ │ ├── reverse_process.md
│ │ ├── elbo_derivation.md
│ │ ├── ddim_derivation.md
│ │ ├── parameterizations.md
│ │ └── system_design.md
│ ├── history/ # evolution of the field
│ └── interview_prep/ # research/implementation/math Q&A
├── mathematics/
│ ├── notation/ # single source of truth for symbols
│ ├── derivations/ # worked, completed-square proofs
│ └── exercises/ # solved practice problems
├── papers/ # one folder per paper, full structure
│ ├── ddpm/ improved_ddpm/ ddim/ score_sde/ latent_diffusion/
│ └── controlnet/ dreambooth/ lora/ consistency_models/
│ flow_matching/ rectified_flow/
├── implementations/
│ ├── layers/ # attention, resblocks, positional embeddings
│ │ ├── positional_embedding.py
│ │ ├── resblock.py
│ │ └── attention.py
│ ├── models/ # UNet, LoRA
│ │ ├── unet.py
│ │ └── lora.py
│ ├── schedulers/ # beta schedules, DDPM, DDIM
│ │ ├── beta_schedules.py
│ │ ├── ddpm_scheduler.py
│ │ └── ddim_scheduler.py
│ ├── samplers/ # Euler, Heun, DPM-Solver (ODE-based)
│ │ └── ode_samplers.py
│ ├── training/ # trainer, EMA, losses
│ │ ├── trainer.py
│ │ ├── ema.py
│ │ └── loss.py
│ └── inference/ # classifier-free-guidance pipeline
│ └── pipeline.py
├── evaluation/metrics/ # FID, Inception Score, CLIP Score
│ ├── fid.py
│ └── clip_score.py
├── experiments/ # real, reproducible small-scale experiments
│ ├── scheduler_comparison/
│ ├── noise_visualization/
│ ├── cfg_scale/
│ └── ablations/
├── research/ # original contribution: adaptive sampler + rigorous study
│ ├── adaptive_sampler.py
│ ├── toy_distributions.py
│ ├── run_experiment.py
│ ├── RESEARCH_NOTE.md
│ └── results.json
├── benchmarks/ # real, measured performance numbers
│ └── results/
├── notebooks/ # executed, reproducible Jupyter notebooks
├── scripts/ # train.py, sample.py, benchmark.py
├── tests/ # 42 passing unit tests
├── assets/diagrams/ # SVG architecture diagrams
│ ├── unet_architecture.svg
│ └── forward_reverse_process.svg
├── datasets/, models/ # dataset/checkpoint conventions (git-ignored contents)
└── references/ # consolidated BibTeX
See docs/history/evolution.md for the full narrative; summary:
| Paper | Year | Key Contribution |
|---|---|---|
| DDPM | 2020 | Noise-prediction objective, UNet architecture |
| Improved DDPM | 2021 | Cosine schedule, learned variance |
| DDIM | 2021 | Non-Markovian fast sampling |
| Score SDE | 2021 | Continuous-time unification, probability-flow ODE |
| Latent Diffusion | 2022 | Diffusion in compressed latent space |
| ControlNet | 2023 | Structural conditioning with zero-conv |
| DreamBooth | 2022 | Subject-driven fine-tuning |
| LoRA | 2021 | Parameter-efficient adaptation |
| Consistency Models | 2023 | Single/few-step generation |
| Flow Matching | 2022 | Simulation-free straight-path training |
| Rectified Flow | 2022 | Straight-line ODE trajectories |
See papers/ for the full folder-per-paper breakdown, and references/bibliography.bib for a consolidated BibTeX file.
git clone https://github.com/your-username/diffusion-models.git
cd diffusion-models
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# or, for an editable install with dev tools:
pip install -e ".[dev,full]"export PYTHONPATH=.
# Train a small model on MNIST (CPU-friendly)
python scripts/train.py --dataset mnist --epochs 5 --base-channels 32 \
--channel-multipliers 1 2 --output-dir runs/mnist_demo
# Sample from it
python scripts/sample.py --checkpoint runs/mnist_demo/checkpoint.pt \
--image-channels 1 --base-channels 32 --channel-multipliers 1 2 \
--sampler ddim --steps 50 --output samples/mnist_grid.png
# Run the test suite
pytest tests/ -v
# Benchmark sampler speed on your hardware
python scripts/benchmark.py --device cpu --image-size 16 --image-channels 1 \
--base-channels 16 --channel-multipliers 1 2 --samplers ddim ddpmfrom implementations.models.unet import UNet, UNetConfig
from implementations.schedulers.ddpm_scheduler import DDPMScheduler
from implementations.inference.pipeline import DiffusionPipeline, SamplingConfig
config = UNetConfig(in_channels=3, base_channels=64, channel_multipliers=(1, 2, 4))
model = UNet(config)
scheduler = DDPMScheduler(num_train_timesteps=1000, beta_schedule="cosine")
pipeline = DiffusionPipeline(model, scheduler, image_shape=(3, 32, 32))
samples = pipeline.sample(
batch_size=4,
config=SamplingConfig(num_inference_steps=50, sampler="ddim", seed=0),
device="cpu",
)See notebooks/ for fully executed, longer walkthroughs.
Real, measured CPU timings (see benchmarks/README.md for full methodology and how to reproduce on your own hardware):
| Sampler | Steps | Wall time (s) | Time/step (ms) |
|---|---|---|---|
| DDIM | 10 | 0.111 | 11.09 |
| DDIM | 50 | 0.567 | 11.34 |
| DDPM | 1000 | 11.209 | 11.21 |
Generated during this repository's own experiment runs (see experiments/noise_visualization/README.md and experiments/scheduler_comparison/README.md for full methodology):
experiments/noise_visualization/forward_process_cosine.png— forward process under the cosine scheduleexperiments/noise_visualization/forward_process_linear.png— forward process under the linear scheduleexperiments/scheduler_comparison/beta_and_alpha_bar.png— beta/alpha_bar curves for four schedules
See implementations/models/unet.py's module docstring for the accompanying explanation.
Unlike the rest of this repository (which reimplements known methods), research/ poses and rigorously tests a genuine question: does runtime-adaptive step-size control (embedded Euler/Heun error estimation, in the style of classical RK45-type ODE solvers) improve diffusion sampling's quality-per-function-evaluation tradeoff over a fixed Karras schedule?
- Analytically exact benchmark: 8-mode ring of Gaussians with closed-form density and exact Tweedie denoiser (zero network approximation error)
- 20 independent seeds per condition for statistical significance
- Wilcoxon signed-rank test comparing adaptive against best fixed baseline
- 5 NFE budgets: 10, 20, 40, 60, 100
- 3 fixed baselines: Euler, Heun, DPM-Solver-2
| NFE budget | Adaptive NLL | Best Fixed NLL | Gap |
|---|---|---|---|
| 10 | 4.18 ± 0.23 | 3.24 ± 0.17 | +0.94 |
| 20 | 2.76 ± 0.10 | 2.71 ± 0.10 | +0.05 |
| 40 | 2.56 ± 0.07 | 2.56 ± 0.07 | 0.00 |
| 60 | 2.55 ± 0.07 | 2.54 ± 0.07 | +0.01 |
| 100 | 2.55 ± 0.07 | 2.53 ± 0.07 | +0.02 |
- Adaptive sampler works reliably at NFE ≥ 50, matching fixed samplers within 0.02 NLL
- Does not outperform the best-tuned fixed schedules at any budget
- Tiered strategy (Euler for <15 NFE, Heun for 15-50 NFE, adaptive for ≥50 NFE) is essential for robustness
- Underutilizes budget at high NFE (stops at 62 NFE vs 100 allowed)
Along the way, this testing uncovered and fixed two real bugs:
- DPM-Solver-2 formula bug: incorrect coefficients in exponential-integrator update, causing samples to collapse to the origin. This was invisible to the existing identity-denoiser unit tests but immediately exposed by the non-degenerate oracle denoiser.
- Adaptive sampler controller bug: accept/reject direction was inverted, and the step-size update used ad hoc fixed multipliers instead of proper error-based control.
The honest result: no. The adaptive sampler underperforms fixed Heun/DPM-Solver-2 at every tested budget (significantly worse at low budgets, roughly matched but not exceeded at high budgets). See research/RESEARCH_NOTE.md for the full writeup, including a concrete explanation for why.
| Experiment | Finding |
|---|---|
experiments/noise_visualization/ |
Cosine schedule visibly preserves more structure than linear at matched timesteps. |
experiments/scheduler_comparison/ |
Direct measurement of alpha_bar_t confirms linear schedule destroys signal fastest. |
experiments/cfg_scale/ |
Measured fidelity/diversity tradeoff as guidance scale increases, on a controlled synthetic task. |
experiments/ablations/ |
Cosine beats linear at matched steps (confirmed); attention did not help on a trivial synthetic task (an honestly-reported negative result, with explanation). |
research/ |
Adaptive step-size control does not beat fixed Karras schedules on an exact toy benchmark (a rigorously tested negative result; see above). |
See CONTRIBUTING.md for the full guide, and CODE_OF_CONDUCT.md for community standards. In short: every new result must be reproducible from code in this repository, every new math component needs a derivation, and every new module needs tests covering its mathematical properties.
If you use this repository in your own work, please cite it as:
@misc{diffusionmodels2025,
title={diffusion-models: A From-Scratch Research Engineering Repository for Diffusion Generative Models},
author={Diffusion Models Contributors},
year={2025},
howpublished={\url{https://github.com/Soyebsoyeb/diffusion-models}}
}For the underlying papers this repository documents and builds on, see references/bibliography.bib.
MIT — see LICENSE.
This repository's paper summaries and mathematical derivations are written independently, in original language, based on public understanding of the cited papers (full citations in references/bibliography.bib); no text is copied from the original papers or any other source. Architectural and algorithmic ideas throughout this repository build directly on the work of the researchers cited in papers/*/README.md and references/bibliography.bib.
See ROADMAP.md for a detailed, honestly-scoped roadmap distinguishing what is implemented and tested today from what is planned. Highlights: a from-scratch autoencoder for a fully self-contained Latent Diffusion pipeline, a dedicated consistency-model trainer, a Flow Matching/Rectified Flow trainer, and GPU-scale benchmark results.