AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Stable Diffusion: Text-to-Image Generation

📚 Generative Models⏱️ 20 min read🎓 Grade 11
✍️ AI Computer Institute Editorial Team Updated: September 2026 CBSE-aligned · Peer-reviewed · 20 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

A Meesho seller in Surat photographs a saree on her bed because she cannot afford a studio. She uploads the photo to an editing app, types "on a marble table, soft window light, e-commerce catalog style," and ten seconds later gets a photograph-quality image with a background that never existed. The app is running Stable Diffusion, or a fine-tuned cousin of it. To understand how this works, resist the urge to ask "how does it learn to draw?" That question has no tractable answer: the space of all possible 512-by-512 pixel images is unimaginably large, and no dataset, however big, tells a network how to place brush strokes for an idea it has never seen. Ask a narrower question instead: how does a network learn to remove noise from a picture, one small amount at a time? That question has a clean, tractable answer, and it is the entire mechanism behind Stable Diffusion.

Reframing generation as denoising

Take a clean image x0 (a real photograph from the training set, represented as a tensor of pixel or latent values). Define a forward process that corrupts it over T discrete steps by repeatedly adding a small dose of Gaussian noise:

q(x_t | x_t-1) = Normal( sqrt(1 - beta_t) * x_t-1 ,  beta_t * I )

Here beta_t is a small positive number (the noise schedule) fixed in advance for every step t from 1 to T. In production Stable Diffusion, T is 1000 and beta_t ranges from 0.00085 at t equal 1 up to 0.012 at t equal 1000, increasing gradually. After enough steps, x_T is indistinguishable from pure Gaussian noise: every trace of the original image is gone. This forward process needs no learning at all; it is just arithmetic plus a random number generator.

The generative model is trained to run this process backward: given a noisy x_t and the timestep t, predict the noise epsilon that was mixed in, so it can be subtracted off. That is the entire supervised learning task. Take a real image, pick a random t, corrupt it to x_t with a known epsilon, and train a network epsilon_theta to output that same epsilon given x_t and t. No labels, no adversarial discriminator, just a regression target that is exactly computable because you generated the noise yourself. Text conditioning is added by also feeding the network a text embedding c, so it learns epsilon_theta(x_t, t, c): predict the noise, given what the final image is supposed to depict.

Worked example: reaching any noise level in one jump

Simulating the forward process one tiny step at a time would be slow and unnecessary, because the composition of many Gaussian corruptions is itself Gaussian, with a closed form. Define alpha_t equal to 1 minus beta_t, and alpha_bar_t equal to the product of alpha_1 through alpha_t. Then, for any x0, you can jump directly to noise level t with a single formula:

x_t = sqrt(alpha_bar_t) * x_0  +  sqrt(1 - alpha_bar_t) * epsilon,   epsilon ~ Normal(0, I)

Let's verify this is not just an asserted formula but a derivable fact, using a small toy schedule with T equal to 2 (real Stable Diffusion uses T equal to 1000 with the schedule above; this toy version is only for tracing the arithmetic by hand). Set beta_1 equal to 0.1 and beta_2 equal to 0.2. Then alpha_1 equals 0.9 and alpha_2 equals 0.8.

Step by step, the forward process says x_1 equals sqrt(alpha_1) times x0 plus sqrt(beta_1) times z1, and x_2 equals sqrt(alpha_2) times x1 plus sqrt(beta_2) times z2, where z1 and z2 are independent standard Gaussian draws. Substitute the first equation into the second:

x_2 = sqrt(alpha_2) * ( sqrt(alpha_1)*x_0 + sqrt(beta_1)*z1 ) + sqrt(beta_2)*z2
    = sqrt(alpha_2 * alpha_1) * x_0  +  sqrt(alpha_2 * beta_1) * z1  +  sqrt(beta_2) * z2

The last two terms are a weighted sum of two independent standard Gaussians, which is itself Gaussian with variance equal to the sum of the squared weights: alpha_2 times beta_1, plus beta_2. Plugging in numbers: 0.8 times 0.1 equals 0.08, plus 0.2, equals 0.28. Compare that to 1 minus alpha_bar_2, where alpha_bar_2 equals alpha_1 times alpha_2 equals 0.9 times 0.8 equals 0.72, so 1 minus alpha_bar_2 equals 0.28 as well. The two match exactly, which is exactly what the closed-form claims: the combined noise from two forward steps collapses into a single Gaussian with variance 1 minus alpha_bar_2. That is why the shortcut formula is valid and not an approximation.

Now use it. Take a single pixel value x0 equal to 0.8 (on a minus 1 to plus 1 scale, a fairly bright value), and a sampled noise value epsilon equal to 0.5. With alpha_bar_2 equal to 0.72, sqrt(alpha_bar_2) equals approximately 0.8485, and sqrt(1 minus alpha_bar_2) equals sqrt(0.28), approximately 0.5292. Then:

x_2 = 0.8485 * 0.8  +  0.5292 * 0.5
    = 0.6788 + 0.2646
    = 0.9434

One multiply-add pair reaches the noise level that two sequential corruption steps would have produced, and this is exactly why real Stable Diffusion training samples a random t between 1 and 1000 for every training image and jumps straight there, rather than looping through a thousand corruption steps for every gradient update.

Why the diffusion runs on latents, not pixels

Running a thousand-step denoising process directly on 512-by-512-by-3 pixel tensors is what earlier diffusion models did, and it is expensive. Stable Diffusion's actual innovation, from the Latent Diffusion Models paper by Rombach and collaborators at LMU Munich in 2022, is to first compress the image with a variational autoencoder (VAE) into a much smaller latent grid, and run the entire denoising process there instead. The encoder downsamples spatially by a factor of 8 in each dimension and expands to 4 channels: a 512-by-512-by-3 image (786,432 numbers) becomes a 64-by-64-by-4 latent (16,384 numbers).

786,432 divided by 16,384 equals exactly 48. The U-Net that does the actual denoising work is therefore operating on 48 times fewer scalar values at every one of its steps than it would on raw pixels, which is the difference between a model that needs a workstation GPU for a few seconds per image and one that would need much more hardware for the same result. The VAE decoder is only invoked once, at the very end, to turn the final denoised 64-by-64-by-4 latent back into a 512-by-512-by-3 image. This is precisely what the name "Stable Diffusion" refers to as distinct from a generic diffusion model: latent diffusion, plus a specific text-conditioning mechanism, described next.

Steering the noise: text conditioning via cross-attention

The text prompt is first tokenized and passed through CLIP's text tower (ViT-L/14 in Stable Diffusion 1.x), a transformer that was itself pretrained to align text and image embeddings, though only its text side is used here, frozen. The output is a sequence of 77 token embeddings, each 768 numbers wide (the sequence is padded or truncated to exactly 77 tokens). This 77-by-768 tensor is the condition c fed into the U-Net.

Inside the U-Net, at several resolutions, cross-attention layers let every spatial location of the image-side feature map look at the text embeddings and decide how relevant each word is to what is being drawn at that location. Concretely: the image feature map is projected into queries Q, and the text embeddings are projected into keys K and values V. The attention weights are softmax of Q times K transpose, divided by the square root of the key dimension, and the output is that weighted combination of V added back into the image features. A patch of the latent that will become "marble table" attends strongly to the tokens for "marble" and "table"; a patch that will become empty background attends more to "soft" and "light." This is why the same U-Net weights can render an unlimited variety of prompts: the image content is not baked into the network's weights, it is looked up from the text embeddings at every one of the roughly 20 to 50 denoising steps used at inference time.

Classifier-free guidance: how hard to listen to the prompt

A network trained only to always obey the prompt tends to produce images that are technically on-topic but visually flat, because the training objective rewards matching the caption more than photographic quality. Stable Diffusion counters this with classifier-free guidance: at every sampling step, the U-Net is run twice, once with the real text embedding and once with an empty-string embedding (representing "no condition"), producing two noise predictions, epsilon_cond and epsilon_uncond. The two are combined by extrapolating away from the unconditional prediction:

epsilon_hat = epsilon_uncond + s * (epsilon_cond - epsilon_uncond)

with s, the guidance scale, typically set around 7 to 8. When s equals 1, this reduces to plain conditional sampling. Pushing s higher exaggerates the direction the text pulls the image toward, which sharpens prompt adherence but past roughly 12 to 15 starts producing oversaturated colors and warped anatomy, because the extrapolation is pushed outside the region of latent space the model was actually trained to denoise accurately. Setting s close to 0 makes the model nearly ignore the prompt and produce generic, unrelated imagery. This single formula is why every Stable Diffusion interface exposes a "guidance scale" or "CFG scale" slider.

The full reverse sampling loop

Putting the pieces together, generating an image means starting from pure noise and repeatedly calling the U-Net to estimate and remove a bit of noise, guided by the text embedding at every step, then decoding once at the end:

import torch

def sample_stable_diffusion(prompt, steps=50, guidance_scale=7.5):
    # text_encoder, unet, vae_decoder, get_ddim_timesteps, ddim_step
    # are assumed helpers, not shown
    cond_embedding = text_encoder(prompt)          # shape (1, 77, 768)
    uncond_embedding = text_encoder("")             # shape (1, 77, 768)

    latent = torch.randn(1, 4, 64, 64)              # x_T: pure Gaussian noise
    timesteps = get_ddim_timesteps(steps)            # e.g. [1000, 980, ..., 20, 0]

    for t in timesteps:
        eps_cond   = unet(latent, t, cond_embedding)
        eps_uncond = unet(latent, t, uncond_embedding)
        eps = eps_uncond + guidance_scale * (eps_cond - eps_uncond)
        latent = ddim_step(latent, eps, t)           # moves latent to timestep t-1

    image = vae_decoder(latent)                       # shape (1, 3, 512, 512)
    return image

Each loop iteration calls the U-Net twice (conditional and unconditional), so a 50-step generation performs 100 U-Net forward passes before the single VAE decode at the very end. The ddim_step helper implements the deterministic DDIM update, which reconstructs an estimate of the clean latent from the current noisy latent and predicted noise, then re-noises that estimate to the next, slightly less noisy timestep, rather than adding fresh randomness at every step the way the original DDPM sampler does. That is what allows Stable Diffusion to skip from 1000 trained noise levels down to a subsequence of 20 to 50 sampling steps without retraining.

Misconception check: does the model paint left to right, like an artist?

A common mental model students bring to this topic is that the network starts with a blank canvas and fills it in progressively, region by region, the way a human painter works one area at a time. This is wrong in an instructive way. Every one of the 4-by-64-by-64 latent values is updated simultaneously at every single denoising step; there is no left-to-right or foreground-to-background order. What actually changes across steps is the scale of detail being resolved, not the spatial region. Early steps, when the latent is still mostly noise, settle broad global structure everywhere at once (where the horizon line falls, how many objects are in frame, the overall color palette). Later steps, once the rough composition has locked in, refine fine texture and edges everywhere at once (fabric weave, individual highlights, small shadows). This is coarse-to-fine, not left-to-right, and it is a direct consequence of the noise schedule: at large t the signal-to-noise ratio is low, so only large-scale patterns survive being combined with heavy noise; at small t the signal-to-noise ratio is high, so fine detail is what remains to be corrected.

The full pipeline

Text Prompt "diya on marble, soft morning light" CLIP Text Encoder ViT-L/14, frozen output: 77 x 768 Latent noise z_T 4 x 64 x 64, pure Gaussian U-Net Denoiser predicts noise eps_theta(z_t, t, c) Cross-attention layers Q = image features (from z_t) K, V = text embeddings (from c) softmax(QK-transpose / sqrt d) . V injected back into z_t text embeddings (K,V) z_t (image query) repeat t = T...1 (DDIM, ~50 steps) Denoised latent z_0 4 x 64 x 64 VAE Decoder upsample x8 Image 512 x 512 x 3 pixels

Active recall

Attempt each question before reading its answer below.

  • 1. Why does Stable Diffusion train the network to predict noise, rather than training it to directly output a clean image from a text prompt?
  • 2. Using the toy schedule beta_1 equal to 0.1, beta_2 equal to 0.2, beta_3 equal to 0.1, compute alpha_bar_3.
  • 3. A Stable Diffusion image is 512 by 512 with 3 channels. Its latent is 64 by 64 with 4 channels. What is the spatial downsampling factor per dimension, and what is the total compression ratio in scalar count?
  • 4. In the cross-attention layers of the U-Net, which tensor supplies the queries and which supplies the keys and values?
  • 5. What happens to image quality if the guidance scale s is set to 1, and what happens if it is set to 20?
  • 6. Why can Stable Diffusion generate an acceptable image in 50 sampling steps at inference time even though it was trained with T equal to 1000 noise levels?

Answers.

1. Predicting the noise is a well-posed regression problem with an exact, self-generated target: you added the noise yourself, so you know precisely what the correct answer is for every training example, at every timestep. Predicting a full clean image directly from a text prompt has no single correct target, since many valid images satisfy the same caption, which is what makes GAN-style direct generation harder to train stably. Denoising sidesteps this by only ever asking "what noise is present," a question with one right answer per corrupted sample.

2. alpha_1 equals 0.9, alpha_2 equals 0.8, alpha_3 equals 0.9. alpha_bar_3 equals 0.9 times 0.8 times 0.9, which is 0.72 times 0.9, equals 0.648.

3. 512 divided by 64 equals 8, so the spatial downsampling factor is 8 per dimension. Total scalar count: pixel space is 512 times 512 times 3, equal to 786,432. Latent space is 64 times 64 times 4, equal to 16,384. The ratio is 786,432 divided by 16,384, equal to 48. The compression is 48 times fewer scalar values, even though the spatial factor is only 8 per side, because the squared spatial reduction (64 times, from 8 times 8) is partly offset by the channel count going up from 3 to 4.

4. The queries (Q) come from the image-side latent features, the ones currently being denoised. The keys and values (K and V) come from the text embeddings produced by CLIP. This asymmetry is what lets the image "ask" the text which words are relevant to each spatial location, rather than the reverse.

5. At s equal to 1, the extrapolation term (epsilon_cond minus epsilon_uncond) is added with weight zero beyond the conditional prediction itself (the formula reduces to plain epsilon_cond), so the image will loosely reflect the prompt but often looks generic or low-contrast, since nothing pushes it away from the model's "average" output. At s equal to 20, the extrapolation is pushed far beyond the region of latent space the network denoises reliably, typically producing oversaturated colors, harsh contrast, and distorted shapes such as extra fingers or warped faces, because the noise estimate is no longer close to what the network was trained to produce.

6. Because of the DDIM sampling formula's determinism: given the current noisy latent and a predicted noise, DDIM can reconstruct an estimate of the fully clean latent and then re-inject exactly the right amount of noise for any target timestep, not only the immediately adjacent one. This lets sampling skip over most of the 1000 trained noise levels and only stop at a subsequence of about 20 to 50 of them, trading a small amount of sample quality for a large reduction in the number of U-Net calls, without any retraining of the network.

Think About It

Think about this: How would you explain stable diffusion: text-to-image generation to a friend who has never seen a computer? What real-world analogy would you use? Imagine you had to build a system using these concepts — what would be your first step? Try this: before moving on, write down three things you learned and one question you still have.

Practice Exercises

Now it is time to practice! Complete these challenges to solidify your understanding:

  • Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
  • Exercise 2: Find a real-world example where stable diffusion: text-to-image generation is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
  • Exercise 3: Create a mind-map connecting stable diffusion: text-to-image generation to at least 3 other topics you have studied.

Key Takeaways — Summary and Recap

Let us recap what we covered: the core ideas behind stable diffusion: text-to-image generation, how they connect to real-world applications, and why they matter for your journey in computer science. Remember these key points as you move forward. For competitive exam preparation (CBSE, JEE, BITSAT), focus on understanding the WHY behind each concept, not just the WHAT.

← CLIP: Vision-Language Pre-trainingVariational Autoencoders: Latent Space Learning →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn