Part III · Ch. 6 — Steering with Words

Part III · Chapter 6 of 8

Steering with Words

Conditioning: turning a prompt into a direction the denoiser can follow


A machine with no ears

We can generate, and we cannot yet steer, because the model has never been told what we want. Chapter 5's machine runs happily without us: pour in a field of random numbers, walk it down the noise schedule, decode, and an apple appears. Possibly red, possibly golden, possibly on a marble counter we never asked for. The noise predictor $\boldsymbol{\epsilon}_\theta(\mathbf{z}_t, t)$ has two inputs, a noisy latent and a noise level, and neither is a place to put a sentence.

This chapter adds one. The standing request throughout is "a green apple on a wooden table." Those seven words become numbers, the numbers enter the U-Net, and one patch of latent decides to listen to "green" rather than "table".

Four stages left to right on a dark background, each with a small muted heading above and a caption below. First, a bordered card holding the prompt text 'a green apple on a wooden table', captioned 'seven words'. An arrow leads to two rows of amber-outlined token chips reading a, green, apple, on above a, wooden, table, captioned 'seven tokens'. An arrow leads to a violet rounded box labeled 'text encoder', captioned 'a sealed box'. An arrow leads to a stack of seven horizontal mint bars, braced on the right and labeled with a large bold mint c, captioned 'one vector per token'.
Seven words in, seven vectors out. The prompt is cut into tokens, the tokens run through a text encoder, and what comes out is the conditioning $\mathbf{c}$, a small stack of vectors, one per token. No image is involved anywhere in this picture.

A prompt becomes numbers

The trip from text to numbers happens in two stages, and you have met both in the sister book.

Stage one: tokens. The sentence is cut into tokens, the chunks a model reads. Ours becomes seven, one per word, which is convenient but not guaranteed, since rarer words split into pieces. The LLM book's Chapter 7 does this properly.

Stage two: the text encoder. Those tokens go into a separate trained network whose only job is to turn them into vectors, the ordered lists of numbers Chapter 1 also called points, which carry meaning in the direction they point, the idea from meaning as direction. It is a relative of the transformer that runs a chatbot rather than the same thing, and this chapter treats it as a sealed box: text in one end, numbers out the other.

What comes out is the conditioning, written $\mathbf{c}$: a set of vectors, one per token. It is a stack rather than one vector for the whole sentence, kept separate so "green" and "wooden" stay individually addressable further down. That separateness is what the next section's arithmetic is about.

The denoiser grows a third input

The structural change is one symbol:

$$\boldsymbol{\epsilon}_\theta(\mathbf{z}_t,\, t,\, \mathbf{c})$$

In words: the noise predictor now takes three things, the noisy latent $\mathbf{z}_t$, the noise level $t$ and the conditioning $\mathbf{c}$, and still hands back one thing.

Be precise about that one thing. The output is still a noise estimate, the same shape as $\mathbf{z}_t$, as in Chapter 3. The prompt does not make the network output an apple. It makes the network output a different guess about the noise. Since Chapter 5's update rule subtracts a fraction of that guess at every sampler step, a different guess walks the latent down a different path, and a different apple comes out of Chapter 4's decoder $\mathrm{Dec}$.

Training is unchanged too. We still manufacture the answer key the way Chapter 3 did, with a real image, a random noise level, a drawn noise blob and a built $\mathbf{z}_t$. The difference is that the training image arrives with a caption, that caption is encoded into $\mathbf{c}$, and the network is handed $\mathbf{c}$ while it guesses. The loss does not change one character. Over millions of examples the network finds that the vectors in $\mathbf{c}$ are worth using, because using them makes its noise guesses better.

A U-shaped diagram. The left arm steps down through three blocks labeled shrink, the bottom is a block labeled bottleneck, and the right arm steps back up through three blocks labeled grow, with three dashed muted skip-connection arrows crossing the middle. A muted label at the top left reads 'noisy latent plus noise level in' with an arrow down into the first block; at the top right, 'predicted noise out' with an arrow up out of the last block. Seven small mint diamonds sit on the outer edge of every block and just below the bottleneck; a legend at the bottom left names them cross-attention blocks. A mint bus runs from a stack of four mint bars labeled c at the bottom center, out along the bottom and up both sides, with a short mint stub into each diamond.
Where the prompt gets in. The conditioning is piped into cross-attention blocks scattered through both arms of the U-Net rather than glued to the input, so the prompt gets consulted at every scale. The coarse blocks near the bottleneck decide what the thing broadly is. The fine blocks near the top decide what its surface looks like.

Patches that ask the prompt

Inside those mint diamonds is attention: a way for one thing to gather information from several candidates, taking more from the relevant ones. Everything inside it plays one of three roles, the same three the LLM book's attention chapter uses. A query ("what am I looking for?"), a key ("here's the kind of thing I am"), and a value (the payload it hands over if picked). Dot products made scores, softmax made shares, and the shares mixed the values. That recipe is unchanged here. What differs is where the roles come from.

Cross-attention takes its queries from the image and its keys and values from the text. Each patch of the latent asks, and every token of the prompt answers. Roles drawn from one sequence are self-attention. Roles split across two different things, a picture asking a sentence, make it cross-attention. Nothing about the arithmetic changes.

Run one. Take a single patch of the latent, one small square in the middle of the fruit, with the two-number query $\mathbf{q} = [1,\, 1]$, and two tokens of our prompt, "green" and "apple". Real vectors are hundreds of numbers long, and two keeps every digit checkable by hand. Every number below is rounded to three decimals.

Who decided green's key is $[1, 1]$? Inside the block, each token's vector is turned into its key by a small learned matrix and into its value by a second one, and the patch's numbers become its query through a third. Those matrices are part of $\theta$, tuned during training like everything else. The numbers below are made up, and small enough to check by hand.

The cast

fromtoken key k value v
the promptgreen[1, 1][2, 0]
the promptapple[1, −1][0, 3]

And from the latent, one patch, supplying only a query: $\mathbf{q} = [1,\, 1]$.

Step one, the scores. Each is a dot product: multiply matching slots, add them up.

$$\mathbf{q} \cdot \mathbf{k}_{\text{green}} = 1 \times 1 + 1 \times 1 = 1 + 1 = 2$$

In words: this patch's question lines up with what "green" advertises, giving a score of 2.

$$\mathbf{q} \cdot \mathbf{k}_{\text{apple}} = 1 \times 1 + 1 \times (-1) = 1 - 1 = 0$$

In words: the same question against "apple"'s label cancels out, giving a score of 0. This patch is asking a color question, and "apple" is not answering it.

Step two, turn the scores into shares totaling 1. That is softmax: raise $e$ to each score, then divide each result by the total of all of them.

Two things there need unpacking. The symbol $e$ is a fixed number, about $2.718$, a mathematical constant like $\pi$ and nothing more than a number we raise to powers. And there are two reasons to raise anything to a power here. Every share has to come out positive, and raising $e$ to a power guarantees that even when the score is negative. It also exaggerates the gaps, so a slightly higher score takes a much larger slice of the patch's attention than plain division by the total would give it.

With $e^{2} = 7.389$ and $e^{0} = 1$, since anything raised to the power 0 is 1, the total is $7.389 + 1 = 8.389$.

$$\mathrm{softmax}(2,\, 0) = \left(\frac{7.389}{8.389},\; \frac{1}{8.389}\right) = (0.881,\; 0.119)$$

In words: this patch spends 88.1% of its listening on the token "green" and 11.9% on "apple". The two shares add to 1.000, which softmax guarantees.

Step three, mix the values using those shares.

$$0.881\begin{bmatrix} 2 \\ 0 \end{bmatrix} + 0.119\begin{bmatrix} 0 \\ 3 \end{bmatrix} = \begin{bmatrix} 1.762 \\ 0 \end{bmatrix} + \begin{bmatrix} 0 \\ 0.357 \end{bmatrix} = \begin{bmatrix} 1.762 \\ 0.357 \end{bmatrix}$$

In words: take 88.1% of green's payload and 11.9% of apple's, and add them. First slot: $0.881 \times 2 + 0.119 \times 0 = 1.762 + 0 = 1.762$. Second slot: $0.881 \times 0 + 0.119 \times 3 = 0 + 0.357 = 0.357$.

That vector $[1.762,\, 0.357]$ folds back into the patch's own numbers before the U-Net carries on. The patch arrived carrying "I am somewhere in a noisy blob" and leaves carrying "and the prompt says my neighborhood is mostly green, with a little apple." Every patch does this, at every scale of the U-Net, at every sampler step.

Two bands separated by a hairline. The upper band holds the ingredients. On the left, under the heading 'from the latent', a four-by-four grid of latent squares with one square filled mint feeds a mint chip reading q equals one, one, captioned 'one patch asks'. On the right, under the heading 'from the prompt', two rows: the token green with an amber chip k equals one, one and a blue chip v equals two, zero; the token apple with an amber chip k equals one, minus one and a blue chip v equals zero, three. The lower band is a left-to-right chain of five chips joined by arrows: scores 2, 0; softmax; shares 0.881, 0.119 in mint; blend the values in blue; and a mint-outlined output chip reading one point seven six two, zero point three five seven. A muted line beneath reads 'the same recipe as self-attention: score, soften, mix'.
The query comes from the picture, the keys and values come from the words. Same colors as the LLM book: mint query, amber keys, blue values. The only structural difference from self-attention is which side of the diagram each role is drawn on.

Green apple, red apple

Change one word and watch the arithmetic move. Swap "green" for "red". A color word still advertises itself as a color word, so its key stays $\mathbf{k}_{\text{red}} = [1,\, 1]$. What it hands over is the opposite end of the color axis: $\mathbf{v}_{\text{red}} = [-2,\, 0]$.

The scores are identical, because the keys are: $1 \times 1 + 1 \times 1 = 2$ against "red" and $0$ against "apple" as before. So the shares are identical too, $0.881$ and $0.119$, and the patch listens in the same proportions. What changes is what it hears:

$$0.881\begin{bmatrix} -2 \\ 0 \end{bmatrix} + 0.119\begin{bmatrix} 0 \\ 3 \end{bmatrix} = \begin{bmatrix} -1.762 \\ 0 \end{bmatrix} + \begin{bmatrix} 0 \\ 0.357 \end{bmatrix} = \begin{bmatrix} -1.762 \\ 0.357 \end{bmatrix}$$

In words: first slot, $0.881 \times (-2) + 0.119 \times 0 = -1.762 + 0 = -1.762$. Second slot, unchanged at $0.881 \times 0 + 0.119 \times 3 = 0.357$.

Read the two results side by side: $[1.762,\, 0.357]$ against $[-1.762,\, 0.357]$. The second number, the appleness, did not move. The first flipped sign. One word swung this patch to the far end of one axis and left everything else alone, which is what "the prompt changed the color but not the subject" means mechanically. Multiply it by thousands of patches and a couple of dozen sampler steps and you get a red apple from the same starting static.

Worked example: adding a style word

A style phrase is another token in the stack, competing for the same shares. Add "watercolor" to the green-apple prompt, with key $\mathbf{k}_{\text{watercolor}} = [0,\, 1]$ and value $\mathbf{v}_{\text{watercolor}} = [1,\, -2]$.

New score: $\mathbf{q} \cdot \mathbf{k}_{\text{watercolor}} = 1 \times 0 + 1 \times 1 = 0 + 1 = 1$. So the three scores are $(2,\, 0,\, 1)$.

Softmax over three: $e^{2} = 7.389$, $e^{0} = 1$, $e^{1} = 2.718$, total $7.389 + 1 + 2.718 = 11.107$. Shares: $7.389 / 11.107 = 0.665$, $1 / 11.107 = 0.090$, $2.718 / 11.107 = 0.245$. They add to $0.665 + 0.090 + 0.245 = 1.000$.

Blend, first slot: $0.665 \times 2 + 0.090 \times 0 + 0.245 \times 1 = 1.330 + 0 + 0.245 = 1.575$.

Blend, second slot: $0.665 \times 0 + 0.090 \times 3 + 0.245 \times (-2) = 0 + 0.270 - 0.490 = -0.220$.

Result: $[1.575,\, -0.220]$, against the two-token $[1.762,\, 0.357]$. The style word did not replace green. It took a share, dropping green's 0.881 to 0.665, and dragged the second slot negative. Every word you add takes a share from every other word, which is the arithmetic behind "long prompts get mushy."

Two runs from the same static

To see what conditioning buys us, run Chapter 5's sampler twice from the same starting latent $\mathbf{z}_T$, with the same seed, the same noise schedule and the same number of sampler steps, changing nothing but the third input.

Run one is unconditioned. We pass the empty prompt $\varnothing$, a real prompt containing no words, run through the same text encoder into a real stack of vectors. The predictor still works, but with nothing to lean on it aims at the average of everything that could look like this, which is a generic, uncommitted image. Run two is conditioned: same $\mathbf{z}_T$, but $\mathbf{c}$ carries "a green apple on a wooden table". At the first sampler step the two predictions already disagree slightly, and since the update rule subtracts a fraction of the prediction, from step two onward the runs stand on different latents and drift apart.

The rest of the chapter is built on one observation: we have both predictions, and the difference between them is a direction. Subtract the unconditioned guess from the conditioned one and what remains is the part of the answer the prompt is responsible for, the greenness and woodenness the empty prompt did not contribute. That difference is a vector we can lean on harder than the model did on its own.

The guidance scale

The technique is called classifier-free guidance. Run the noise predictor twice at every sampler step, once with the prompt and once with the empty prompt, then push away from the unconditioned answer in the direction of the conditioned one, and keep pushing past it. The amount of push is the guidance scale $s$.

$$\hat{\boldsymbol{\epsilon}} = \boldsymbol{\epsilon}_\theta(\mathbf{z}_t, t, \varnothing) + s\left(\boldsymbol{\epsilon}_\theta(\mathbf{z}_t, t, \mathbf{c}) - \boldsymbol{\epsilon}_\theta(\mathbf{z}_t, t, \varnothing)\right)$$

In words: start at the unconditioned noise guess, measure the step from there to the conditioned noise guess, multiply that step by $s$, and take it. The result $\hat{\boldsymbol{\epsilon}}$, read "epsilon-hat", is what we hand to Chapter 5's update rule in place of a plain prediction.

Three values of $s$ are worth the algebra, because the middle one is the most misread number in the field.

At $s = 0$, write $\boldsymbol{\epsilon}_\varnothing$ for the unconditioned guess and $\boldsymbol{\epsilon}_{\mathbf{c}}$ for the conditioned one:

$$\hat{\boldsymbol{\epsilon}} = \boldsymbol{\epsilon}_\varnothing + 0\left(\boldsymbol{\epsilon}_{\mathbf{c}} - \boldsymbol{\epsilon}_\varnothing\right) = \boldsymbol{\epsilon}_\varnothing + 0 = \boldsymbol{\epsilon}_\varnothing$$

In words: multiply the step by zero and you never leave the starting point. The prompt is thrown away entirely and you get the unconditioned image.

At $s = 1$, the term in brackets is taken once:

$$\hat{\boldsymbol{\epsilon}} = \boldsymbol{\epsilon}_\varnothing + 1\left(\boldsymbol{\epsilon}_{\mathbf{c}} - \boldsymbol{\epsilon}_\varnothing\right) = \boldsymbol{\epsilon}_\varnothing + \boldsymbol{\epsilon}_{\mathbf{c}} - \boldsymbol{\epsilon}_\varnothing = \boldsymbol{\epsilon}_{\mathbf{c}}$$

In words: the two copies of $\boldsymbol{\epsilon}_\varnothing$ cancel, one added and one subtracted, and what is left is the conditioned prediction on its own.

Sit with that line, because it is where most explanations go wrong. $s = 1$ is the model's unamplified answer to your prompt, the prediction it would make if you had passed $\mathbf{c}$ and never run the second pass. Guidance switched off is $s = 0$, which gives an image with no prompt in it. Between the two you fade the prompt in. Above 1 you exaggerate it.

At $s > 1$ we extrapolate past the conditioned answer. Work it on one number, one coordinate of one latent at one sampler step. Say the unconditioned prediction there is $0.60$ and the conditioned prediction is $0.50$. The difference is

$$0.50 - 0.60 = -0.10$$

In words: knowing the prompt moved this coordinate's noise guess down by one-tenth. That tenth is the prompt's entire contribution here.

At a guidance scale of $s = 7.5$, a common default:

$$\hat{\epsilon} = 0.60 + 7.5 \times (-0.10) = 0.60 + (-0.75) = 0.60 - 0.75 = -0.15$$

In words: multiply the prompt's one-tenth of influence by seven and a half to get $-0.75$, add that to the unconditioned $0.60$, and land on $-0.15$.

The two predictions the model made were $0.60$ and $0.50$, and the guided value $-0.15$ is nowhere near either. We took a small, trustworthy direction and stretched it seven and a half times. At $s = 3$ we would have landed on $0.60 + 3 \times (-0.10) = 0.60 - 0.30 = 0.30$, a shorter stretch of the same move.

A two-dimensional plot with axes labeled 'noise coordinate 1' and 'noise coordinate 2'. A muted dot at coordinate 0.60, 0.20 is labeled 'unconditioned prediction, the empty prompt'. A blue dot at 0.50, 0.35 is labeled 'conditioned prediction, the prompt'. A short amber arrow runs from the muted dot to the blue dot, drawn just beneath them so it stays visible, and is labeled 'the difference: what the prompt contributes'. A long mint arrow starts at the muted dot, passes straight through the blue dot, and ends at a mint dot at 0.30, 0.65 labeled 'guided estimate at s = 3'. A faint shaded ellipse enclosing only the two prediction dots is labeled 'where the predictor was ever accurate'; the mint dot lies well outside it.
Guidance is extrapolation, not interpolation. The amber arrow is the only piece of real information: the difference between two predictions the model made. Everything past the blue dot is us multiplying that difference and hoping the answer still means something. It does, up to a point, and stops after it.

So what breaks at high $s$? The noise predictor was trained on latents from real images at real noise levels, and its guesses are reliable in that neighborhood. Extrapolating seven or twenty times past that answer marches the estimate out of the region where it was ever checked against reality, and the sampler follows it there. In pictures that shows up as blown-out color, scorched contrast, hard edges where things should be soft, and rigid, literal obedience to the prompt. Low $s$ gives weak adherence, where the prompt is a suggestion the image half-takes. The useful band for most models runs roughly 5 to 9, with $s = 7.5$ a common default. Those are typical configuration choices rather than laws.

Four square photographs in a row on a dark background, headed s = 1, s = 3, s = 7.5 and s = 20, all made from the same seed and the prompt 'a green apple on a wooden table'. At s = 1 a small yellow-green apple sits in a muddle of torn cloth and blue streaks, with only a strip of wood beneath it. At s = 3 a plain green apple sits on a worn wooden table. At s = 7.5 a glossy green apple with a stem sits on the table in soft light. At s = 20 the apple is a harsh, oversaturated green, the table glows orange and the background is blown out. The captions beneath read prompt half-ignored, gently followed, the useful band, and scorched and rigid.
The same seed and the same prompt at four guidance scales. Left to right: the prompt half-followed, with the table barely there; a plain apple on a plain table; the useful band; and the extrapolation breaking into oversaturated color and hard contrast. Rendered with Stable Diffusion 1.5 from the prompt "a green apple on a wooden table", seed 1234, 25 DDIM sampler steps.

Nothing requires the second pass to use the empty prompt. Swap $\varnothing$ for a real encoded prompt like "blurry, extra stems" and the formula pushes away from that instead. That is all a negative prompt is.

See it move

Watch (18:35): the patch's mint query meeting the two amber keys, the scores 2 and 0 becoming shares 0.881 and 0.119, and then the guidance arrow shooting straight through the conditioned prediction and out the far side as $s$ climbs.

Where you'll meet this

Open any Stable-Diffusion-style workflow graph and this chapter is drawn on the screen. There are two CLIP Text Encode nodes, not one. Each is the sealed text-encoder box from earlier: a prompt goes in, and out comes a wire labeled CONDITIONING that carries $\mathbf{c}$. Both wires run into the sampler node, into sockets marked positive and negative. The positive socket is $\mathbf{c}$. The negative socket is what stands in for $\varnothing$ in the guidance formula, which is why leaving it blank is a normal thing to do: blank is the empty prompt.

On the sampler node sits a field labeled cfg, usually defaulting near 7 or 8. That is $s$, the multiplier on the gap between the two predictions the sampler is already computing. It is neither a quality dial nor a prompt-strength dial. When someone's images look "burned", that is the first number to check, and you now know why. They are reading a calibration curve far outside the range it was built on.

Two everyday experiences come out of this arithmetic. Prompt crowding, where a twelve-word prompt honors each word less than a four-word one, is the softmax shares splitting further, the way our style word dropped green's 0.881 to 0.665. And phrasing mattering more than it "should" is the text encoder producing a different $\mathbf{c}$ for a different sentence. There is no keyword matching in the pipeline to be robust for you.

There is a hard edge to all of it. Every one of these knobs steers the model within what it already knows. If you want the heirloom apple variety in your garden, or one illustrator's line quality, no prompt reaches it. The text encoder will encode your words, the cross-attention will blend them, and the model will produce its best generic guess, because nothing in $\theta$ ever saw your apple. So how do you teach a fixed, finished model one new specific thing without retraining all of it?

What you now know

  • A prompt becomes numbers in two stages, tokens then a text encoder, and what comes out is the conditioning $\mathbf{c}$, one vector per token, with no image and no caption database anywhere in the process.
  • The noise predictor gains a third input and becomes $\boldsymbol{\epsilon}_\theta(\mathbf{z}_t, t, \mathbf{c})$, and its output is still a noise estimate. The prompt changes the guess, not the kind of thing being guessed.
  • Cross-attention is the LLM book's attention with the roles split across two sources: the query comes from a latent patch, the keys and values from the prompt tokens. Our patch scored 2 against "green" and 0 against "apple", giving shares of 0.881 and 0.119 and a blended output of $[1.762,\, 0.357]$.
  • Swapping "green" for "red" left the shares untouched at 0.881 and 0.119 and flipped the blend to $[-1.762,\, 0.357]$. One axis moved and the appleness did not.
  • Classifier-free guidance runs the predictor twice and extrapolates: $\hat{\boldsymbol{\epsilon}} = \boldsymbol{\epsilon}_\varnothing + s(\boldsymbol{\epsilon}_{\mathbf{c}} - \boldsymbol{\epsilon}_\varnothing)$. At $s = 0$ it collapses to the unconditioned prediction and at $s = 1$ to the plain conditioned one, so $s = 1$ is not "off", while a typical $s = 7.5$ turned $0.60$ and $0.50$ into a guided $-0.15$.
  • Guidance costs a second U-Net pass at every sampler step, roughly doubling the work.

Where we're headed. You can now aim the machine. The same starting static and the same seed will give you a green apple or a red one on demand, and you know which number to turn when the color goes scorched. What you cannot do is ask for something the model has never encountered, like your particular apple variety or your friend's illustration style, because conditioning only selects among directions that $\theta$ already contains, and no value of $s$ can amplify a direction that isn't there. The fix is a small trained add-on called a LoRA, which leaves the base model frozen and learns a small correction to a few of its weight matrices.