Hello! Welcome back to your course on diffusion models.
In the last lesson, we demystified how a text prompt guides image generation by implementing text conditioning via cross-attention. We established that the U-Net's image features (Query) attend to the text embeddings (Key, Value) to incorporate semantic guidance.
Now that we can tell the model what to generate, let's learn how to tell it what not to generate, and how to emphasize or de-emphasize specific details. Today's learning outcome is to implement negative prompting and prompt re-weighting to refine generation control. These are not just neat tricks; they are fundamental techniques for steering the diffusion process, crucial for moving from generic outputs to precisely controlled, high-quality results. Understanding their implementation will give you a significant degree of control when building and debugging your own models.
Part 1: Negative Prompts and Classifier-Free Guidance (CFG)
At its simplest, a negative prompt is a list of concepts you want to exclude from your generated image. Common examples include terms like "blurry, low quality, distorted, extra limbs" to avoid artifacts, or "trees, buildings" to control the background of a scene.
Let's start by seeing why this is so important, especially in modern models.
Please use NEGATIVE PROMPTS with Stable Diffusion v2.0
The YouTube channel '1littlecoder' provides an excellent demonstration of the dramatic impact negative prompts have on generation quality, particularly with models like Stable Diffusion 2.0.
Watch the first part of this video (00:00 - 03:38). Notice the stark difference in the generated images with and without negative prompts. The presenter also touches on why this became more important in later models, which is an interesting practical insight.
The improvement is undeniable. But how does this work? The model doesn't have a separate "negative prompt" input. The magic lies in a technique called Classifier-Free Guidance (CFG).
The Mechanism of Classifier-Free Guidance
CFG is a clever method that allows a conditional diffusion model to improve its adherence to the conditioning signal (e.g., a text prompt) without needing a separate classifier network.
The core idea involves two key phases:
-
Training: The model is trained on conditional inputs (e.g., an image and its text description). However, a certain percentage of the time (e.g., 10-20%), the conditioning signal is randomly dropped and replaced with a generic "unconditional" token. This forces the model to learn to denoise both with and without guidance.
-
Inference (Sampling): At each denoising step, we perform two forward passes through the U-Net:
- One pass conditioned on our text prompt to get a noise prediction, let's call it .
- A second pass conditioned on the unconditional token (e.g., an empty string "") to get an unconditional noise prediction, .
The final noise prediction, , is an extrapolation. We take the unconditional prediction and push it in the direction of the conditional one:
Here, is the guidance scale (or CFG scale).
- If , we get the unconditional prediction.
- If , we get the conditional prediction.
- If (e.g., a common value is 7.5), we are extrapolating, pushing the result further in the direction of our prompt than what the model saw during training. This amplifies the prompt's influence, leading to images that align more strongly with the text.
The following video explains this mechanism in the context of class-conditioning, but the principle is identical for text-conditioning.
Diffusion Models | PyTorch Implementation
The 'Outlier' channel has a concise explanation of how CFG is implemented. While it uses class labels for conditioning, the formula and the logic of unconditional training and dual-pass inference are exactly what we use for text.
Watch the segment on Classifier-Free Guidance (15:57 - 19:14). Focus on the training strategy (randomly setting labels to none) and the final interpolation formula used during sampling. This is the core of CFG.
From CFG to Negative Prompts
The leap to negative prompts is surprisingly simple. Instead of using a generic empty string for the "unconditional" pass, we use our negative prompt.
The process becomes:
- Get the text embeddings for the positive prompt (
"A majestic lion"). This gives uscontext_pos. - Get the text embeddings for the negative prompt (
"blurry, cartoon, low-resolution"). This gives uscontext_neg. - At each sampling step, do two forward passes:
- One with
context_posto get . - One with
context_negto get .
- One with
- Calculate the final noise prediction:
This formula intuitively tells the model: "Start with what I don't want, and move away from it towards what I do want."
Implementation in the Sampling Loop
Given your background, you can see how this maps to a PyTorch implementation. Here's the pseudocode for a single sampling step:
def sample_step(noisy_latents, t, positive_prompt, negative_prompt, guidance_scale):
# 1. Get text embeddings from the encoder
c_pos = text_encoder(positive_prompt)
c_neg = text_encoder(negative_prompt)
# 2. Duplicate latents and concatenate contexts for a single batch forward pass
latent_model_input = torch.cat([noisy_latents, noisy_latents])
context = torch.cat([c_neg, c_pos])
# Add time embeddings
time_emb = get_time_embedding(t)
time_emb = torch.cat([time_emb, time_emb])
# 3. Perform two forward passes in one go
# The U-Net's forward signature is forward(x, context, time)
noise_preds = unet(latent_model_input, context, time_emb)
# 4. Separate the predictions
noise_pred_neg, noise_pred_pos = noise_preds.chunk(2)
# 5. Apply the CFG formula
final_noise_pred = noise_pred_neg + guidance_scale * (noise_pred_pos - noise_pred_neg)
# 6. Use the final prediction to denoise the latents (as in a standard DDPM/DDIM step)
# ...
return denoised_latents
This is a highly efficient implementation as it leverages batching to perform both forward passes simultaneously.
Part 2: Prompt Re-weighting
Negative prompting is about excluding concepts entirely. Prompt re-weighting provides more subtle, fine-grained control by increasing or decreasing the influence of specific words within your positive prompt.
For example, in the prompt "A painting of a red car", you might want to make the car more red, or ensure the "painting" style is strongly expressed.
Prompt Engineering for Stable Diffusion
The blog 'Prompt Engineering for Stable Diffusion' from Portkey.ai introduces the user-facing syntax for token weighting and provides clear examples.
Read the sections introducing 'Token weighting' and 'Stable Diffusion Prompt Syntax', including the first example 'Using Token Weighting for Emphasis'. Focus on the (word:weight) syntax. This is how users typically interact with this feature.
As the article shows, a common syntax is (a majestic castle:1.3). This instructs the model to pay 30% more attention to "a majestic castle". How do we implement this? There are two main approaches.
Approach 1: Heuristic (Embedding-level Manipulation)
Many popular implementations (like Automatic1111's web UI) parse this syntax and manipulate the text embeddings before they are sent to the U-Net. This is handled by libraries that sit between the user's prompt string and the text encoder. For example, a weight of 1.3 might be implemented by duplicating the corresponding text tokens in the sequence fed to the text encoder, effectively making them appear more often. This is a practical but indirect way of influencing the model.
Approach 2: Principled (Attention-level Manipulation)
A more direct and powerful method is to manipulate the cross-attention maps themselves during the U-Net's forward pass. This was explored in the influential "Prompt-to-Prompt" paper.
The core idea: in the previous lesson, you learned that cross-attention calculates a score for how much each pixel should "attend" to each word token. By directly scaling these scores for a specific word, we can precisely control its influence on the final image.
Prompt-to-Prompt Image Editing with Cross-Attention Control
The 'Prompt-to-Prompt' project page gives a fantastic overview of what's possible when you control cross-attention maps directly. We will focus on their 'Attention Re-weighting' technique.
Read the sections 'Prompt-to-Prompt Image Editing', 'Cross-Attention Control', and look at the 'Attention Re-weighting Examples'. Note how they can amplify or attenuate the 'fluffiness' of a bunny doll by directly manipulating attention.
The implementation of this principled approach involves intercepting the attention calculation within your CrossAttention module.
Here's the logic:
- In your
CrossAttentionmodule, after calculating the attention scores but before applying softmax: - Identify the index
jof the token you want to re-weight (e.g., the token for "red"). - Multiply the scores corresponding to that token by your desired weight
c.# Inside CrossAttention forward pass # attention_scores has shape (batch, heads, seq_len_q, seq_len_kv) weight = 1.3 token_index_to_reweight = 4 # Example: index for 'red' attention_scores[:, :, :, token_index_to_reweight] *= weight - Apply the softmax function to the modified scores.
- Proceed with the rest of the attention calculation (
attention_probs @ V).
This method gives you surgical control over the influence of each token. It requires modifying your CrossAttention module or using PyTorch hooks to inject this logic, which is a very powerful technique for an ML engineer to have in their toolkit.
Test your understanding!
You want to generate an image for the prompt a photo of a happy dog. However, the model keeps generating dogs that look sad. You decide to use both negative prompting and prompt re-weighting.
Your positive prompt is a photo of a (happy:1.4) dog and your negative prompt is sad, gloomy.
Describe the computations you would perform inside a single sampling step to get the final noise prediction , assuming you are using the principled attention-map manipulation for re-weighting.
Show answer
-
Get Embeddings: Encode the positive prompt (
"a photo of a happy dog") to getcontext_posand the negative prompt ("sad, gloomy") to getcontext_neg. You would also identify that the token for "happy" is at index 4 (for example) in the positive prompt's token sequence. -
Batch Inputs: Create a single batch for the U-Net.
latent_model_input = torch.cat([noisy_latents, noisy_latents])andcontext = torch.cat([context_neg, context_pos]). -
Modified Forward Pass: During the U-Net's forward pass, when the
CrossAttentionmodule is called for the positive prompt context, you would apply the re-weighting logic.- Calculate the raw attention scores:
scores = Q @ K.transpose(...). - Before the softmax, you would multiply the scores for the "happy" token by 1.4:
scores[..., 4] *= 1.4. - Then, apply softmax:
attn = softmax(scores). - The rest of the forward pass for both positive and negative contexts proceeds normally.
- Calculate the raw attention scores:
-
Get Predictions: The U-Net will output two noise predictions in a single tensor. You separate them:
noise_pred_neg, noise_pred_pos = unet_output.chunk(2). -
Apply CFG: You calculate the final prediction using the CFG formula with your desired
guidance_scale:final_noise_pred = noise_pred_neg + guidance_scale * (noise_pred_pos - noise_pred_neg). This final value is then used to denoise the latents.
Conclusion
Today, you've learned how to implement two of the most important techniques for controlling text-to-image diffusion models. These methods are essential for practical applications, allowing you to debug artifacts and steer generation with high precision.
Key Takeaways:
- Negative Prompting is powered by Classifier-Free Guidance (CFG): By replacing the unconditional prediction with one conditioned on a negative prompt, we can steer the generation away from unwanted concepts.
- CFG Implementation: This involves a dual forward pass during inference (one for the positive prompt, one for the negative) and combining the results with a guidance scale.
- Prompt Re-weighting offers fine-grained control: It allows you to amplify or suppress the influence of specific words within your prompt.
- Re-weighting Implementation: This can be done at the attention level by directly scaling the attention scores for specific tokens before the softmax operation, offering a principled way to control semantic influence.
Next Lesson Preview:
We've just discussed manipulating cross-attention maps to control the output. This raises a crucial question: how can we see what the model is attending to? In the next lesson, we will dive into exactly that. We will learn how to analyze cross-attention maps to debug prompt alignment failures, giving you a powerful diagnostic tool to understand why a model might be ignoring or misinterpreting parts of your prompt.