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. We cannot yet steer β€” 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. A fine apple β€” possibly red, possibly golden, possibly on a marble counter we never asked for. The noise predictor $\boldsymbol{\epsilon}_\theta(\mathbf{z}_t, t)$ has exactly two inputs β€” a noisy latent and a noise level β€” and neither is a place to put a sentence.

This chapter bolts on a steering wheel. Our standing request throughout is "a green apple on a wooden table." By the end you will know what happens to those seven words: how they become numbers, where those numbers enter the U-Net, how one patch of latent decides to listen to "green" rather than "table", and what the guidance number in your workflow is actually multiplying.

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 chopped into tokens, the tokens run through a text encoder, and what comes out the far side 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 actually 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 β€” ordered lists of numbers, the same object Chapter 1 also called a point β€” that carry meaning in the direction they point, the idea from meaning as direction. It is a cousin of the transformer that runs a chatbot, not the same animal, and we treat 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. Not one vector for the whole sentence β€” a stack, kept separate, so "green" and "wooden" stay individually addressable further down. That separateness is what the next section spends all its arithmetic on.

The denoiser grows a third input

Here is the entire structural change, and it 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, because it is the easiest sentence in this book to get wrong. The output is still a noise estimate, the same shape as $\mathbf{z}_t$, exactly 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 β€” real image, random noise level, drawn noise blob, built $\mathbf{z}_t$ β€” except the training image now 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 discovers 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 not glued to the input. It is piped into cross-attention blocks scattered through both arms of the U-Net, 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 a mechanism you already own, and here it is in one breath, because I promised to define everything I use: attention is a way for one thing to gather information from several candidates, taking more from the ones that are relevant to it and less from the ones that are not. Everything inside it plays one of three roles β€” the same three the LLM book's attention chapter uses, so if you have read that, this is a reunion: 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, shares mixed the values. That entire recipe is unchanged here. One thing differs β€” 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; every token of the prompt answers. Same-sequence roles are self-attention; roles split across two different things β€” a picture asking a sentence β€” make it cross-attention. Nothing about the arithmetic changes.

Let's 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". As in the attention chapter, real vectors are hundreds of numbers long; two keeps every digit checkable by hand and changes nothing. Every number here is rounded to three decimals, and every intermediate is printed.

One honest note on where those numbers come from, because "who decided green's key is $[1, 1]$?" is the right question to ask. 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 β€” the same trick, twice β€” and the patch's numbers are turned into its query by a third. Those matrices are part of $\theta$, tuned during training like everything else in the model. The numbers below are hand-picked stand-ins, small enough to check; what matters is what happens to them.

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 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 well 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 exactly, giving a score of 0 β€” this patch is asking a color-ish 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 in that sentence need unpacking. The first is the symbol $e$, which is a specific fixed number, about $2.718$ β€” a mathematical constant like $\pi$, and for our purposes nothing more mysterious than that: a number we raise to powers. The second is why we raise anything to a power at all, and there are two reasons, which together are the whole justification. Every share has to come out positive, and raising $e$ to a power guarantees that even when the score is negative. And it exaggerates the gaps, so a slightly higher score takes a much larger slice of the patch's attention than a plain division by the total would give it.

With $e^{2} = 7.389$ and $e^{0} = 1$ β€” 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"; it leaves carrying "…and the prompt says my neighborhood is overwhelmingly green, with a dash of 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

Now 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]$ β€” but 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$ β€” 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: the mechanical meaning of "the prompt changed the color but not the subject." 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 works the same way β€” it is just 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 β€” green's 0.881 fell to 0.665 β€” and dragged the second slot negative. Every word you add takes a share from every other word, which is the honest 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 identical starting latent $\mathbf{z}_T$ β€” same seed, same noise schedule, 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; it just has nothing to lean on, so it guesses the way a witness with no description guesses β€” aiming at the average of everything that could look like this: a generic, plausible, 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, ask different questions, and drift apart. Two paths, one origin.

Here is the observation the rest of the chapter is built on: we have both predictions, and the difference between them is a direction. Subtract the unconditioned guess from the conditioned one and what remains is exactly 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}}$ β€” "epsilon-hat" β€” is what we hand to Chapter 5's update rule in place of a plain prediction.

Three values of $s$ deserve the algebra done out loud rather than asserted, 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 exactly 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, one subtracted β€” and what is left is exactly the conditioned prediction, on its own.

Sit with that line, because it is where most explanations go wrong. $s = 1$ is not guidance switched off. It is the model's honest, 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. Suppose 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, and we will see in a moment where the useful band lies:

$$\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$.

Look at where that lands. The two predictions the model actually made were $0.60$ and $0.50$; the guided value $-0.15$ is not between them and nowhere near them. 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 honest predictions. Everything past the blue dot is us multiplying that difference and hoping the answer still means something β€” which it does, up to a point, and stops meaning something 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 the honest answer marches the estimate out of the region where it was ever checked against reality, and the sampler dutifully follows it there. In pictures that shows up as blown-out color, scorched contrast, hard edges where things should be soft, and rigid, literal, poster-like obedience to the prompt. Low $s$ gives weak adherence β€” 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, not laws.

A red-outlined banner across the top reads: PLACEHOLDER DIAGRAM β€” the real strip needs four renders from an actual model. Below it, four panels in a row, each holding the book's apple sitting on a table line, headed s = 1, s = 3, s = 7.5 and s = 20 and captioned prompt half-ignored, gently followed, the useful band, and scorched and rigid. The first apple is amber and faded β€” the word green has barely been followed. The second is a muted green. The third is a crisp, fully saturated green apple. The fourth is the same green but stretched tall and stiff. A line beneath reads 'same seed, same prompt β€” only the guidance scale changes'.
The same seed and the same prompt at four guidance scales. Left to right: the word "green" barely followed, the prompt gently followed, the useful band, and the extrapolation breaking β€” flat scorched color and a stiff, literal shape. This figure is currently a hand-drawn placeholder; the real strip needs four renders from an actual model.

One last note. 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 β€” which is all a negative prompt is.

See it move

Watch (18:35): what to notice β€” 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 wire 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 perfectly normal thing to do β€” blank is the empty prompt, exactly as derived.

On the sampler node sits a field labeled cfg, usually defaulting near 7 or 8. That is $s$ β€” not a quality dial and not a prompt-strength dial, but the multiplier on the gap between the two predictions the sampler is already computing. When someone's images look "burnt", 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 straight out of this chapter's arithmetic. Prompt crowding β€” a twelve-word prompt honouring each word less than a four-word one β€” is the softmax shares splitting further, exactly as our style word dropped green's 0.881 to 0.665. And phrasing mattering more than it "should" is the text encoder producing a genuinely different $\mathbf{c}$ for a different sentence; there is no keyword matching anywhere 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? Hold that question.

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, 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 β€” $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 β€” your particular apple variety, 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 tiny correction to a few of its weight matrices: the model will follow any prompt it already understands, and not one it has never seen.