๐ŸŽ“ A1Learner
๐Ÿ”

PyTorch A Tiny Diffusion Model

A tiny diffusion model is a small network that predicts the noise inside a noised point at a given step. Removing the predicted noise step by step turns pure noise back into data-like points.

A random step per row

Each training row draws its own step number from 0 to 19. The model must handle every noise level, not one fixed level.

torch.manual_seed(0)
print(torch.randint(0, 20, (4,)).tolist())

The model predicts the noise

The target for the model is the exact noise that was added. Mean squared error scores the prediction against that noise.

ab = alpha_bars[t]
noisy = ab.sqrt().unsqueeze(1) * x0 + (1.0 - ab).sqrt().unsqueeze(1) * noise
loss = nn.functional.mse_loss(model(noisy, t), noise)

Walking backwards

Sampling starts from pure noise and removes one predicted step at a time. Fresh noise is added back at every step except the last one.

x = (x - beta / (1.0 - ab).sqrt() * noise_hat) / alpha.sqrt()
if ti > 0:
    x = x + beta.sqrt() * torch.randn(5, 2)

Example

import torch
from torch import nn

steps_total = 20
betas = torch.linspace(0.05, 0.40, steps_total)
alphas = 1.0 - betas
alpha_bars = torch.cumprod(alphas, dim=0)

torch.manual_seed(0)
per_blob = 256
blob_a = torch.randn(per_blob, 2) * 0.5 + torch.tensor([-2.0, 0.0])
blob_b = torch.randn(per_blob, 2) * 0.5 + torch.tensor([2.0, 0.0])
data = torch.cat([blob_a, blob_b])


class Denoiser(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(3, 64), nn.ReLU(),
            nn.Linear(64, 64), nn.ReLU(),
            nn.Linear(64, 2))

    def forward(self, x, t):
        level = (t.float() / steps_total).unsqueeze(1)
        return self.net(torch.cat([x, level], dim=1))


torch.manual_seed(3)
model = Denoiser()
optimizer = torch.optim.Adam(model.parameters(), lr=0.005)
batch = 256
for step in range(400):
    rows = torch.randint(0, data.shape[0], (batch,))
    x0 = data[rows]
    t = torch.randint(0, steps_total, (batch,))
    noise = torch.randn(batch, 2)
    ab = alpha_bars[t]
    noisy = ab.sqrt().unsqueeze(1) * x0 + (1.0 - ab).sqrt().unsqueeze(1) * noise
    loss = nn.functional.mse_loss(model(noisy, t), noise)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

print("Final noise-prediction loss:", round(loss.item(), 4))

torch.manual_seed(42)
x = torch.randn(5, 2)
with torch.no_grad():
    for ti in reversed(range(steps_total)):
        t = torch.full((5,), ti, dtype=torch.long)
        noise_hat = model(x, t)
        beta = betas[ti]
        alpha = alphas[ti]
        ab = alpha_bars[ti]
        x = (x - beta / (1.0 - ab).sqrt() * noise_hat) / alpha.sqrt()
        if ti > 0:
            x = x + beta.sqrt() * torch.randn(5, 2)

print("Generated points:")
for row in x.tolist():
    print(f"({row[0]:.2f}, {row[1]:.2f})")
print("Real points:")
for i in [0, 100, 256, 356, 511]:
    row = data[i].tolist()
    print(f"({row[0]:.2f}, {row[1]:.2f})")

Key points

  • Denoiser: the network that predicts the noise in a noised point
  • Training covers all 20 steps because each row draws a random step
  • The generated points land near the two blob centres at (-2, 0) and (2, 0)
  • Five generated points print next to five real points in the closing program

In this example, the schedule holds 20 steps with betas rising from 0.05 to 0.40. The data is 512 points in two blobs around (-2, 0) and (2, 0). The denoiser is a small MLP that takes the two point coordinates and the step fraction as its three inputs. Each of the 400 rounds trains on a batch of 256 rows. Every row draws a random step and fresh noise. The final noise-prediction loss prints as 0.3036. Sampling then starts five points as pure noise under a seed and walks all 20 steps backwards. The five generated points print next to five real points. Most generated points land near a blob centre. Two land in the inner tails between the centre and a blob.

Note

Twenty steps and 400 training rounds keep this model tiny, so some generated points land between the blobs

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Python Playground

Exercise

What does a diffusion denoiser learn to output for a noised point?