Writing

An SRE's guide to deploying Large Language Models, Part 1: understand the workload

· updated Oct 23, 2026 #llms#sre#transformers#inference

I work as an SRE at Careem, and I recently started exploring how to deploy and serve LLM workloads effectively. This is a series of guides on what I’ve learned so far, and this is part 1 of that guide.

As SREs, we can deploy and ensure the reliability of a workload only because we build a good understanding of its semantics, which lets us generalize the requirements and conditions of that workload. Take the one workload that undeniably every SRE deploys today: microservices. We’re able to run them reliably because we inherently understand that a microservice is a long-running process, potentially connected to one or more data sources or another microservice, exposed over a network socket and communicating over a particular protocol, e.g. HTTP. This tells us what a healthy microservice looks like, i.e. error rate and latency should stay low, and a rise in either warrants a scale-up or a restart, which is what lets us run them reliably. Understanding the workload is a fundamental requirement for running it reliably, so this part is dedicated to understanding LLMs as a workload.

On the surface, LLMs look like microservices, because invoking one looks like you’re just talking to an HTTP endpoint, but they’re absolutely nothing like a regular microservice. Under the hood, an LLM is a hyper-parallelized matrix-multiplication beast that takes some know-how of machine learning, specifically deep learning, which I’m not going to go into. Frankly, understanding transformers opens the Pandora’s box of neural networks, gradient descent, matrices, linear algebra, and probability and statistics: a very dry and steep learning curve, one that’s hard to justify for SREs. ML engineers spend a good portion of their studies mastering all of this, and if you can, I do highly recommend building at least a high-level understanding when you have the time and energy. Once the concepts click, they’re fairly simple, and also fascinating. If you do decide to go down this path, I highly recommend Grant Sanderson’s videos on attention, which helped me build a mental model of what’s going on:

Transformers, the tech behind LLMs | Deep Learning Chapter 5 3Blue1Brown
Attention in transformers, step-by-step | Deep Learning Chapter 6 3Blue1Brown
How might LLMs store facts | Deep Learning Chapter 7 3Blue1Brown
Visualizing transformers and attention | TNG Big Tech Day '24 Grant Sanderson

But today I’m only going to touch these subjects superficially, and try to explain them in the simplest terms I can. With one exception: a quick refresher on three mathematics concepts, small tools from linear algebra that everything inside a transformer is built out of. They take a couple of minutes, and once you have them, the rest of this article is just those three tools applied over and over.

Three Mathematics Concepts Refresher first

refresher 1

An array of 2 numbers is a vector in 2D space.

An array of two numbers is a vector in 2D space The array three, two on the left and a plane with x and y axes on the right. A cyan arrow grows from the origin to the point three, two, and dashed guides drop to the axes labelled three and two. Two grey ghost arrows show that other arrays are other points. 3 2 x y [-2, 1] [1, -2] 3 2 [3, 2]

refresher 1

3 numbers: a vector in 3D space.

A vector in three dimensions The array three, two, one is drawn as an arrow in a 3D space with x, y and z axes: three along x, one toward the viewer along z and two up along y. A third number appears on its own and adds a third dimension, then the camera turns slowly to show the depth. 3 2 1 3 numbers x y z [3, 2] [3, 2, 1]
Two numbers are a point, and the arrow from the origin to it. The array [3, 2] and the arrow are the same thing.

The first tool is the vector. A vector is just an array of numbers, and what makes it useful is that you can draw it. Two numbers, like [3, 2], are a point on a plane, three along x and two up along y, and the arrow from the origin to that point is the vector. Three numbers are a point in 3D space, and nothing stops you from adding more. You can’t draw a vector of 5,120 numbers, but the arithmetic doesn’t care, and as we’ll see shortly, that is exactly how long the vectors inside our model are.

refresher 1

A vector can be multiplied by a matrix.

1 1 x 1 0 0.5 1
= ? ?
1.5 = 1x1 + 1x0.5
1 = 1x0 + 1x1

This operation is called a linear transformation.

A vector multiplied by a matrix The row vector one, one times the matrix one, zero, zero point five, one. The grid of the plane shears sideways, the grey vector v stays fixed, and the cyan result arrow lands at one point five, one. v v′

refresher 1

Different matrices transform the same vector differently.

1 1 x 1.00 0.00 0.00 1.00
= 1.00 1.00
Different matrices transform the same vector The multiplication one, one times a matrix, beside a grid of the plane. In an endless loop, every two seconds the matrix changes: the identity, two shears and a stretch. The grid warps, the grey vector v stays fixed and the cyan vector, the result, moves with each matrix. v v′
Multiplying a vector by a matrix gives a new vector. This operation is called a linear transformation. For this matrix the arrow shears sideways: [1, 1] becomes [1.5, 1].

The second tool is the matrix, a grid of numbers. Multiply a vector by a matrix and you get a new vector: the operation is called a linear transformation, and the matrix moves the arrow. This one shears [1, 1] sideways into [1.5, 1]; a different matrix would stretch it, squash it or rotate it instead. Every matrix is a particular linear transformation, and a big part of training, as we’ll see, is finding the matrices that move vectors in useful ways.

refresher 2

The dot product: one number for how similar two vectors are.

The dot product: one number for how similar two vectors are A plane with the fixed grey vector a = [2, 1] and the cyan vector b of length sqrt(5) rotating around the origin. Beside it, the fixed array [2, 1] and the live array of b with matching entries linked, the live arithmetic 2 x b1 + 1 x b2, and a gauge from minus 5 to plus 5 whose marker and big number track the live dot product. a b a 2 1 b 1.0 2.0 2 x 1.0 + 1 x 2.0 +4.00 −5 0 +5
The dot product multiplies matching entries and adds them up. Pointing the same way gives a big positive number, at right angles zero, and opposite ways a negative one.

The third tool is the dot product. It takes two vectors and gives back a single number: multiply the matching entries and add them up. That number tells you how much the two arrows point the same way. Pointing the same way gives a big positive number, at right angles you get zero, and pointing in opposite directions gives a negative number.

refresher 3

What if each dimension is an idea?

Ideas as dimensions A plane whose horizontal axis runs from forest to desert and whose vertical axis runs from non-living to living. Sand is a cyan arrow into the lower right (desert and non-living), rock is a cyan arrow pointing almost straight down (non-living), and fern is a grey arrow into the upper left (forest and living). Each arrow is also shown as a small mix of its two ideas.

refresher 3

Sand and rock point the same way, so their dot product is positive.

Dot product of ideas The same plane of ideas: forest to desert across, non-living to living up. Sand and rock are cyan arrows that point the same way, both into the non-living half, so their dot product, worked out beside the plane as 0.80 times 0.15 plus minus 0.60 times minus 0.95, is plus 0.69 on a gauge from minus one to plus one. Then rock turns grey and fern, which points the opposite way, is compared with sand: the dot product is minus 0.92.

refresher 3

Sandstone sits between sand and rock. A transformation gets us there.

A transformation moves sand to sandstone The same plane of ideas, with a dashed target marked sandstone between sand and rock. Multiplying the sand arrow by the matrix M1 shears it toward rock until it lands on sandstone. Resetting and multiplying the rock arrow by a second matrix M2 shears it toward sand and lands on the same point. Both routes end as dashed grey trails on sandstone.
If each dimension is an idea, a vector is a mix of ideas. Sand is desert and non-living; rock is almost purely non-living. (A toy: real dimensions are never this readable.)

Here’s why these three tools matter for language. Imagine each dimension of a vector stands for an idea. Say the first number measures how desert-like something is (negative means forest) and the second how living it is (negative means non-living). Sand becomes [0.80, -0.60], rock [0.15, -0.95] and fern [-0.70, 0.60]. The dot product of sand and rock is positive, because both are non-living, while the dot product of sand and fern comes out at -0.92, because they’re opposite ideas. And a matrix can shear sand toward rock and land on sandstone, a new idea made out of two old ones. Real models never have dimensions this readable, but this is the whole game: words become vectors, dot products measure how related they are, and matrices transform them into new meanings. Keep these three in your pocket; everything that follows is built out of them.

The loop

So what exactly is an LLM? It’s just a collection of billions and billions of numbers. This is why, when you’re told a model is “27B parameters”, the 27 billion is really the count of numbers in that model. To be fair, they’re not a bag of randomly placed numbers; they’re vectors and matrices, which means each number is part of an ordered group. The next natural question is: what do we do with these numbers? We use them inside an algorithm, and in this case that algorithm is the famous transformer.

the loop

A transformer: a box that predicts the next word.

A transformer: a box that predicts the next word Five word chips, The cat sat on the, point with an arrow at a teal box labelled transformer, which points with a second arrow at a dashed output slot. The slot shows a question mark, then the predicted word mat in cyan. The cat sat on the transformer ? mat

the loop

Append the word. Feed it back. Repeat.

The generation loop with words A strip of word chips, The cat sat on the, feeds a transformer box that outputs one word. Each output word travels back along the dashed append path into the strip, until the end token appears and the loop arrow disappears. The cat sat on the mat . <end> transformer ? append
Give a transformer a sentence and it predicts the next word.

What does the transformer algorithm do? In simple terms, given a partial sentence, a single run of it predicts what the next most probable word (or partial word) would be. When we invoke these models, we’re really running the transformer in a loop: given a sentence, generate the next word, append that word to the original sentence, and re-run the whole sentence through the transformer to generate the next word. We keep the loop running until the next word is one we’ve designated as the end of a sentence.

Tokens

tokens

Models read tokens, not words.

Words versus tokens The sentence I can dependably deploy LLMs is five words. The real Qwen3.8-27B tokenizer splits it into eight tokens: dependably breaks into two pieces, LLMs into three, and a dot marks a leading space. = leading space
Models read tokens, not words. Qwen3.8-27B's real tokenizer turns these 5 words into 8 tokens. The dot marks a leading space, which is part of the token.

I’ve been talking about predicting words, but it’s not really words, it’s tokens, which differ from words. A token can be a word, but it can also be a piece of a word, and tokens also include spaces and punctuation marks. In other words, a token is a sequence of one or more Unicode characters. Transformers process tokens: input sentences are first broken down into tokens, passed through the transformer, and the transformer outputs the next probable token. The concept of tokens is so vital to large language models that both their performance (tokens/sec) and their cost (some $ per M tokens) are measured in tokens.

tokens

A downloaded model is just a folder of files.

The real Qwen3.8-27B folder: a vocabulary (tokenizer.json), a config, and 55.6 GB of numbers in 18 safetensors shards. To follow along you only need the first two.

At this point it’s worth downloading an open-weight model, prying it open, and inspecting it. I’ll use Qwen3.8-27B throughout this series, because it’s the model I run in production. The full download is 55.6 GB of numbers split across 18 files, but to follow along you only need two small files from it. Make sure you have Python and pipx installed on your system, fire up a terminal, and run the following:

pipx install hf
hf download Qwen/Qwen3.8-27B config.json tokenizer.json --local-dir ./qwen

tokens

The vocabulary: every token the model can read or write.

tokens

The tokenizer turns text into vocabulary numbers.

The tokenizer maps text to vocabulary ids The sentence I can dependably deploy LLMs goes into the tokenizer and comes out as eight token chips. Under each chip appears its id in the vocabulary, 40, 628, 6549, 2752, 10204, 436, 10673 and 82, and a small excerpt of the vocabulary shows three of those lookups. I can dependably deploy LLMs tokenizer vocabulary
The vocabulary is a numbered list: 248,044 tokens plus 33 special ones. Punctuation and letters come first, then words and word pieces from many languages. (Real entries from the file.)

Once downloaded, open the qwen/tokenizer.json file in a text editor. This is a JSON file; navigate to the model.vocab property. This is the full vocabulary of tokens, which means these are the possible tokens that can be generated from any sentence: 248,044 of them, plus 33 special tokens the model uses for control. A tokenizer is an algorithm that converts a sequence of characters into tokens. For example, the sentence:

I can dependably deploy LLMs

will be tokenized as:

[40, 628, 6549, 2752, 10204, 436, 10673, 82]

tokens

A word that is not in the vocabulary is split into smaller pieces.

Unknown words are split into known pieces Four words, Careemify, unbelievably, supercalifragilistic and an Arabic greeting, each shown as one chip that cracks apart into the pieces the real Qwen3.8-27B tokenizer uses, with the vocabulary id of every piece underneath. Earlier words stay above in a smaller size.
A word that is not in the vocabulary is split into pieces that are, so rare words just cost more tokens. (Real ids. The Arabic word reads right to left, so its first piece is on the right.)

The numbers are the values in the model.vocab dictionary, keyed against the actual tokens the sentence was broken down into. The interesting bit is that even though we have a five-word sentence, the token count is eight. This is because if a word doesn’t have a direct mapping to a number, it’s further broken down into smaller tokens. This is why 6549 is Ġdepend and 2752 is ably, and why 436 is ĠL, 10673 is LM and 82 is s, which, when combined, make LLMs with its leading space (in the vocabulary file a leading space shows up as Ġ; the figures mark it with a dot). This is essentially the base idea of every tokenizer: keep breaking down the character sequence until each piece maps to something in the vocabulary. Longer words simply take more tokens: “unbelievably” comes out as un, belie and vably, and “supercalifragilistic” takes seven pieces.

Embeddings

embeddings

Each token number is a row in a huge table.

The embedding table of Qwen3.8-27B: one row per token number Eight token chips with their real ids 40, 628, 6549, 2752, 10204, 436, 10673 and 82. Below them a huge heat-grid table of 248,320 rows by 5,120 columns, rows ordered by id. Each chip drops a line to its own row. Row 628 then lifts out as a long strip showing its first eight real numbers.

embeddings

The row length is the model's hidden_size.

$ jq .text_config.hidden_size config.json 5120
hidden_size of three Qwen models Three horizontal bars whose lengths are the hidden sizes 2,560 for Qwen3-4B, 4,096 for Qwen3-8B and 5,120 for Qwen3.8-27B, the one just read from config.json. Beside each name the shape of its embedding table: vocabulary size by hidden size.
A token's number is a row index into one huge table: 248,320 rows of 5,120 numbers, 1.27 billion in all. Row 628 is " can", and these are its real first 8 values. (The grid is a stylised sample.)

Next, what do these numbers mean, and how are they used? For that, open qwen/config.json and find a property named hidden_size under text_config. For our model, the value should be 5120. Every model has an internal representation of each token in its vocabulary, and this representation is an array of numbers: a vector, our first tool. What is the length of this vector? That’s right, it’s equal to 5120, the value of hidden_size. This vector of 5,120 numbers is called the embedding of that token. The embeddings of all possible tokens are stored as a huge table, 248,320 rows of 5,120 numbers each, in the model’s .safetensors files, and the numbers we got above as the output of tokenization are the indexes into that table. If I want the embedding for ” can”, I look at row 628 of that table. That one table alone is 1.27 billion numbers, about 2.5 GB, before the model has done any thinking at all.

embeddings

Each row is a vector: a mix of ideas.

A row of the embedding table is a vector: a mix of ideas A barcode of the real first 64 numbers of row 628, the token can, with the first four bars labelled idea 1 to idea 4 and the count 5,120. Then a toy plane appears with two ideas, desert and living, and an arrow camel built from the two weights 0.75 and 0.55. ... first 64 of 5,120 numbers row 628 = token " can" desert living toy: 2 ideas instead of 5,120 camel [0.75, 0.55] 0.75 0.55 camel = 0.75 desert + 0.55 living
Each number says how much of one idea the token carries. On the left, the real first 64 numbers of " can"; on the right, the toy camel: 0.75 desert plus 0.55 living.

Now, what exactly are embeddings, and why specifically 5,120 numbers in each? This is where the ideas-as-dimensions picture from earlier pays off. The trainers of the model decide what the dimensions of each embedding are going to be, which in our case is 5,120. This size is decided based on how much the trainer wants to trade off between capability and efficiency; smaller models in the same family use 2,560 or 4,096.

That trade-off isn’t guesswork. It follows what are called scaling laws. In 2020, researchers at OpenAI showed that a language model’s error falls smoothly and predictably as you grow three things: the number of parameters, the amount of training text, and the compute spent on training. They also found that the total number of parameters matters far more than how it’s spent, whether on longer vectors or on more layers. In 2022, DeepMind’s Chinchilla study refined the recipe: for a fixed training budget, the parameters and the training text should grow together, at roughly 20 tokens of training text per parameter. That’s why a bigger model in a family gets both a longer hidden_size and more layers: Qwen3-4B pairs 2,560 dimensions with 36 layers, while our model pairs 5,120 with 64. And here’s the part that matters to an SRE: labs now deliberately train small models far past that recipe. Llama 3’s 8B model was trained on about 15 trillion tokens, nearly a hundred times the Chinchilla figure, because a smaller model that knows more is cheaper to serve, and for a popular model the cost of serving it, paid on every request, soon outgrows the one-time cost of training it.

Each number in an embedding is a dimension value, and a dimension of a token is meant to represent some aspect, property, or idea related to that token. For example, if I say “camel”, one dimension in its embedding might represent “desert” and another “living”, and the higher the value, the more of that idea the word carries: a camel might be 0.75 desert and 0.55 living. In our model we have 5,120 dimensions per token, which means 5,120 knobs or properties per token. But let me clarify that this example is never real, and only meant to give you a grasp of what a dimension is. In reality, each dimension rarely represents one clean idea or property, and we can see this clearly by looking at how we even come up with these values in the first place.

Training

training

At the start, every number is random.

The embedding table before training A window into the embedding table, 248,320 rows by 5,120 columns, filled with random numbers. Then four tokens, cat, dog, car and king, appear as arrows in a plane, pointing in random directions. random numbers random numbers, random directions

training

Take a sentence from the training data and split it in two.

A training sentence split into input and answer The sentence The cat sat on the mat as six chips. A cyan line splits it before mat: the first five words are the input, mat is the answer. The input goes into a transformer box, which guesses dog, and dog is compared with the real answer mat: they are not equal. input answer The cat sat on the mat mat transformer dog guess !=

training

The error says how far off the guess was.

The model's probabilities against the true answer Six bars show the untrained model's probabilities for mat, rug, floor, chair, table and dog: nearly flat, with dog highest at 30 percent and mat at 12 percent. A dashed outline marks the target: mat should be 100 percent. The gap between the mat bar and the target is the error, 88 percent, which tells us to change the numbers. error 100% − 12% = 88% change the numbers
Before training, the embedding table is noise: random numbers pointing in random directions. Nothing about cats or dogs is in there yet.

To train a large language model, we generally start with a large amount of textual data we intend to train the model on. We then initialize these embedding vectors with completely random numbers. At a high level, the training process is crudely the following:

  • First, break the textual data down into small sentences.
  • Each sentence is then split into two parts. The first part, along with all the random numbers, is fed into an algorithm (a neural network) whose job is to predict the second part of the sentence. Feed it “The cat sat on the” and an untrained model might guess “dog”, when the real next word is “mat”.
  • On the very first runs of this algorithm, we get very random predictions for what the second part is. When this happens, the algorithm checks how far off it is from the actual result: if the model gave “mat” only 12% when the answer was “mat” with certainty, the error is large.

training

The error tells us how much to change each number.

Error plotted against one number w A U-shaped curve of the error against a single number w, with its minimum at w equals 3. A point starts on the left slope at w equals 0.5. A tangent line shows the slope, minus 5. The point then steps downhill by 0.3 times the slope, to w equals 2.0, and keeps stepping: each step is smaller as the curve flattens near the bottom. w 0.50 error 6.75 slope = derivative −5.00 w -= 0.3 * slope step size 0.60

training

Nudge every number a little, millions of times.

Random embeddings become meaningful clusters Twenty-four token dots with labels start at random positions in a plane. A step counter ticks up to 1,000,000 while the dots move into four clusters: animals, vehicles, royals and food. Similar tokens end up close together. A toy with 2 dimensions instead of 5,120. step 0 toy: 2 dimensions instead of 5,120
The slope of the error says which way to nudge a number, and by how much: steep means a big nudge, flat means we are close. (A toy error curve over one number w, with a step size of 0.3.)

Then comes the step that makes it learn. The algorithm nudges every number in proportion to how much it contributed to the error. For each number, the slope of the error says which way to move it and by how much: a steep slope means a big nudge, a flat one means it’s almost right. This is called gradient descent. Repeat it millions of times and the numbers stop being random: similar tokens end up pointing similar ways, animals near animals and vehicles near vehicles. Once the guesses are right to an acceptable degree, the tuned arrays become the embeddings of the tokens in that model.

Notice how the tuning of these dimensions depends solely on the large amount of textual data they were trained on; they’ve been tuned to predict that specific data. For a completely different dataset, these embeddings would come out completely different. Now, what do we do with these embeddings? We use them to predict what comes next, but that alone is not enough.

Inside the transformer

inside the transformer

Open the box: four stages.

The four stages inside a transformer A pipeline of four blocks, each with a small icon: 1 embedder (token chips looked up as a table row), 2 attention (arcs between tokens), 3 feed-forward (a bank of detectors), 4 sampling (dice). The blocks light up one after another; blocks 2 and 3 are bracketed with the word repeats. repeats

inside the transformer

Attention and feed-forward repeat, layer after layer.

The attention plus feed-forward block repeats 64 times First the pipeline: an embedder, one block called attention plus feed-forward, and sampling. Then the middle block is stacked into 64 thin slabs, shown as the first three, three dots and the last one, labelled x 64 layers, with a small vector arrow flowing down through them before it reaches sampling.
Inside the box are four stages: the embedder we just met, attention and feed-forward doing the thinking, and sampling picking the word.

Before going further, here’s the map of where we’re heading. Open up the transformer and there are four stages. The embedder is what we just covered: tokenize the text and look each token’s vector up in the table. Then come two stages that do the actual thinking, attention and feed-forward, which repeat together as one block, 64 times in our model. Finally, sampling picks the next token. Let’s take the middle two in turn.

Attention

attention

The same word can mean different things.

One token, two meanings The sentence I'm tarballing the oddball photos of golf balls, split by the real Qwen3.8-27B tokenizer into twelve tokens with their ids. The two ball tokens are both id 3826 and are linked by a bracket: same token, same row, same vector. Then the first ball is labelled tarball and the second oddball. same token, same row, same vector tarball oddball = leading space

attention

Each ball is the same vector, transformed in a different direction.

One vector, three transformations in 3D A 3D scene with x, y and z axes, tick marks and a faint floor grid. A grey arrow, ball, leaves the origin. One after another, the arrow is sheared into three cyan arrows pointing in three different directions: golf ball, oddball and tarball, each with a dashed trail from the tip of ball. Finally all three are shown together and the camera turns gently so that the three directions can be told apart.

attention

Each token is influenced by the other tokens.

Tokens influence each other The twelve tokens of the sentence in a row. Arcs run from the other tokens into one chosen token and the thickness of an arc is the size of the influence. First ball receives a thick arc from tar, the second ball a thick arc from odd, and balls a thick arc from golf. Thin faint arcs come from other tokens.
The real Qwen3.8-27B tokenizer on "I'm tarballing the oddball photos of golf balls". Both "ball" pieces are token 3826, so the model starts them from the very same vector.

The natural question here is: we already have these tuned embeddings per token, so aren’t they enough to guess the next token? Well, no. Take the sentence “I’m tarballing the oddball photos of golf balls”. The tokenizer splits it into tar and ball, odd and ball, and golf and balls, and both of those balls are the same token, 3826. They start out as the very same row of the embedding table, the very same vector. Yet one is a tarball, the other an oddball, and the last is golf balls: same word, three different meanings. Words change what they mean based on the other words they’re used with in a sentence. So when the transformer is guessing what comes next, it needs not just a numerical representation of the tokens, but also a numerical representation of how each token is influencing the meaning of every other token. In vector terms, each ball has to be pushed in its own direction by the tokens around it.

attention

Three matrices: Wq, Wk and Wv.

The three attention matrices Wq, Wk and Wv Three big grids of numbers side by side, labelled Wq, Wk and Wv with the real shapes of layer 3 of Qwen3.8-27B: q_proj 12,288 by 5,120, k_proj 1,024 by 5,120 and v_proj 1,024 by 5,120. At first the cells are random noise. Training settles them into a pattern. Then the embedding of a token, a row of 5,120 numbers, enters and is multiplied into each of the three matrices.

attention

Embedding × Wq = query: what this token is looking for.

The embedding of ball times Wq is its query The row vector e of ball, one and zero, is multiplied by the matrix Wq and gives the query of the first ball, 0.95 and 0.10. In the plane the grey arrow e of ball turns into the cyan arrow q of ball, which points slightly upward. A speech bubble says the query is looking for a word that modifies the token.
Three more tables of numbers, Wq, Wk and Wv, start random and are tuned in training like everything else. Applied to the same embedding, they give a query, a key and a value. (Real Qwen3.8-27B shapes, where Wq's 12,288 outputs include a gate as well as the queries. The cells are a toy picture.)

This problem is one of the keys that the transformer addresses. Specifically, the algorithm introduces three more matrices, Wq, Wk and Wv, initialized randomly and tuned during training just like everything else. Each is applied to the same embedding and gives a different vector. Multiplying a token’s embedding by Wq gives its query, Q, which represents what this token is looking for in the rest of the sentence. With toy numbers in two dimensions, the first ball is [1.00, 0.00], and multiplying it by Wq gives the query [0.95, 0.10], which you can read as: I’m looking for a word that modifies me.

attention

Embedding × Wk = key: what kind of influence this token has to offer.

The embedding of tar times Wk is its key The row vector e of tar, 0.20 and 0.90, is multiplied by the matrix Wk and gives the key of tar, 0.90 and 0.15. In the plane the grey arrow e of tar turns into the cyan arrow k of tar. The keys of odd and golf appear as two more cyan arrows. A speech bubble says the key offers: I am a modifier.

attention

Embedding × Wv = value: the influence this token can pass on.

The embedding of tar times Wv is its value The row vector e of tar, 0.20 and 0.90, is multiplied by the matrix Wv and gives the value of tar, 0.20 and 0.60. In the plane the grey arrow e of tar shortens into the cyan arrow v of tar. The values of odd and golf appear as two more cyan arrows. A speech bubble says the value is an influence: make the ball tar-like.
The key is the embedding times Wk: what kind of influence this token has to offer. Tar's key says: I am a modifier.

Multiplying by Wk gives the key, K, which represents what kind of influence this token has to offer, i.e. what it can be matched against: tar’s key comes out as [0.90, 0.15], which reads as: I am a modifier. And multiplying by Wv gives the value, V, the influence this token will have on another token if the two match: tar’s value, [0.20, 0.60], is what would make a ball tar-like. There’s one difference between these matrices and the embeddings: an embedding is only tuned when its own token appears in a training sentence, whereas Wq, Wk and Wv are tuned on every sentence. Because of that, they end up capturing the relationships between tokens in a sequence rather than any single token.

attention

q · k: how strongly one token is influenced by another.

Query times key scores as a table and as arrows On the left, a table with the keys tar, odd, golf, ball one, ball two and balls as rows and the queries of ball one, ball two and balls as columns. Each cell is the dot product of a query and a key. On the right, the same keys as grey arrows in a plane with the selected query as a cyan arrow. For each query the key with the highest dot product lights up: tar for the first ball, odd for the second ball, golf for balls.

attention

Softmax turns the scores into weights that sum to 1.

Softmax turns four scores into four weights The scores of the first ball against the keys tar, odd, golf and itself are shown as bars: 0.87, 0.23, minus 0.76 and 0.08. Softmax turns them into weights, 0.46 for tar, 0.24 for odd, 0.09 for golf and 0.21 for the ball itself. The weights sum to 1.00, and tar has the biggest weight.
Every cell is one dot product: how well this token's query lines up with that token's key. The first ball lines up with tar, the second with odd, and balls with golf. (Toy numbers; real models also weigh how far apart the tokens are.)

At inference time, every token gets its query, key and value from these matrices. Now, remember that Q holds what a token is looking for, and K holds what kind of influence a token has to offer. We take the query of each token and compute its dot product, our third tool, with the key of every other token. The result is a single number that represents how much one token has to offer to another; the higher the number, the more shared context the two tokens have. That’s exactly what the score table shows: the first ball’s query lines up best with tar’s key at 0.87, the second ball’s with odd’s, and balls with golf. Those raw scores then go through a softmax, a way of normalizing them into proportions: raise e to each score and divide by the total, so they’re all positive and add up to 1. For the first ball, tar gets a weight of 0.46, odd 0.24, golf 0.09 and the token itself 0.21.

attention

Blend in the values, weighted, and add them to the token.

The weighted values are added to the embedding of ball In the attention plane, the grey arrow e of ball points right. The values of tar, odd and golf are first shown at full size, then scaled by the softmax weights 0.46, 0.24 and 0.09, then stacked head to tail into their sum, and finally the sum is added to e of ball. The result is the cyan arrow tarball, and a curved arrow shows how the embedding of ball is sheared into it.

attention

Same token, different vectors: context made the difference.

Attention gives the same token different vectors The sentence as twelve token chips, with the two ball tokens and the balls token underlined in three matching styles. In the attention plane all three start from their embedding, the first two from exactly the same arrow. After attention they point in three different directions: the first ball becomes tarball, the second becomes oddball and balls becomes golf balls.
The weights decide how much of each value to add: tar's counts most, golf's barely. Sum them, add the sum to the ball's embedding, and the vector is sheared toward tarball. (In this toy the ball's own value is zero.)

The next step is to multiply each of those weights by the value of the token that supplied the key. Remember that V represents the influence a token has on others, so scaling it by the weight gives how much of that influence should be applied: tar’s value counts the most, golf’s barely at all. Finally, these weighted values are added together, and the result is added onto the original token’s embedding: the first ball moves from [1.00, 0.00] to [1.23, 0.25], toward tarball. Run the same process for the other two, and the second ball becomes an oddball while balls moves toward golf. Both balls started as the very same vector, and context made them different. Now each token represents not just itself, but also bits of information from the other tokens in the sequence. The algorithm we just followed is called attention. (I’ve simplified a few things. Real queries, keys and values have thousands of numbers. Real models also take token positions into account, so nearby words weigh more, and a token only ever looks back at the tokens before it, never ahead, which the toy ignores to keep the numbers small; part 2 leans on that rule. And in Qwen3.8-27B most attention layers use a cheaper linear-attention variant. The idea is the same.)

Feed-forward

feed-forward

Some answers are not in the sentence.

The answer is not in the question The question Who won the 2026 FIFA World Cup? split into its twelve real tokens, the last token, the question mark, highlighted. Below, a plane with the horizontal axis a country and the vertical axis football: the vector of the question mark is a short arrow that mostly points at football and barely at any country. A faint marker for Spain sits far away at the upper right with a question mark. In the second step a dashed gap joins the arrow to Spain, with the words: nothing in the sentence says Spain. nothing in the sentence says Spain
Some answers are not in the sentence. Nothing in "Who won the 2026 FIFA World Cup?" says Spain, so something has to move this vector toward it. (Real tokenization; a toy vector on two readable axes.)

But there’s a second problem. Sometimes, part of a sentence is going to be a fact. For example, consider the sentence: “Who won the 2026 FIFA World Cup?”. We know it was Spain, but if you look at the original sentence, it doesn’t contain “Spain” as a token anywhere. So just making tokens attend to each other is not going to magically lead us to Spain; something has to move the token’s vector toward Spain, using knowledge from somewhere other than the sentence. What we do know is that during training there would have been many sample sentences carrying this relationship as context.

feed-forward

Feed-forward has two matrices: Wu widens the vector, Wd narrows it back.

Wu widens the vector, Wd narrows it back A left-to-right chain. A token vector of 5,120 numbers goes through Wu, a wide matrix, and comes out much longer, 17,408 numbers. That long vector goes through Wd, a tall matrix, and comes out as 5,120 numbers again. In the first step Wu and the widening light up, in the second Wd and the narrowing, and in the last step each matrix is shown to hold 89,128,960 numbers. two matrices: Wu and Wd Wu widens the vector: 5,120 → 17,408 numbers Wd narrows it back: 17,408 → 5,120 numbers each matrix holds 5,120 × 17,408 = 89,128,960 numbers
Wu widens 5,120 numbers to 17,408, and Wd brings them back. In between, something happens to the long vector: the next figures open each piece.

So after the part that does attention, we introduce another pair of matrices, called Wu and Wd. They’re the biggest matrices in each layer: Wu widens the vector, with 17,408 columns of 5,120 numbers each (intermediate_size, sitting right next to hidden_size in config.json), and Wd narrows it back the other way.

feed-forward

Wu: each column is a question about the token.

Wu is a bank of detectors The token vector x on the left feeds a bank of six columns of Wu, shown under a bracket reading 17,408 of them in the real model. Each column asks a question: is it about Spain, is it football, is it about a trophy, is it 2026, is it a capital city, is it about food. Taking the dot product of x with each column grows a bar: 0.93, 0.81, 0.64, 0.58, 0.12 and 0.03. The Spain bar is highlighted with the note that the training data said the 2026 World Cup went to Spain.

feed-forward

Silence the weak matches.

Weak matches are silenced Six bars for the detector magnitudes Spain 0.93, football 0.81, trophy 0.64, 2026 0.58, capital 0.12 and food 0.03, with a horizontal threshold line at 0.5. In the second step the two bars below the threshold drop to zero, leaving a faint ghost, and the array of magnitudes becomes 0.93, 0.81, 0.64, 0.58, 0, 0. A small plot on the right shows the same rule: values below 0.5 go to zero, values above pass through.

feed-forward

Wd: how much to shear the vector for each detected pattern.

Wd shears the vector toward Spain On the left, the idea plane with the horizontal axis a country and the vertical axis football, the grey arrow x0 and a marker for Spain. On the right, the four rows of Wd, spain, football, trophy and 2026, each with the magnitude that survived the silencing. Step by step each row arrow is scaled by its magnitude, the four scaled arrows are stacked head to tail into the change delta, and delta is added to x0: the vector lands on Spain.
Each column of Wu is a question about the token, scored with a dot product. Training text in which Spain won the 2026 World Cup left one column that fires on exactly this question. (Toy questions; the real ones are not human-readable.)

So what did training actually put into these two matrices? Two complementary halves of the same fact. Wu became a rack of detectors: each of its 17,408 columns is a pattern of context, a question about the token, and multiplying the token by Wu scores how strongly each pattern is present right now, one dot product per column. A large positive number means “this pattern is here”, and anything small means “not this one”. Silencing the weak matches removes the noise, so only the patterns that really fire get through. Wd is the paired write-back: for every detector in Wu, Wd stores a direction, the information to add into the token when that detector fires. Put plainly, Wu asks “what situation is this?” and Wd answers “then write this in.”

Walk our example through it. Somewhere in training, “Spain” kept turning up in the neighbourhood of “the 2026 FIFA World Cup … won by”. Gradient descent settles one of Wu’s columns into a detector that lights up on exactly that “who won the 2026 World Cup” situation, and settles the paired Wd direction to point toward Spain. Nobody labels either slot; it falls out of the model getting better at predicting the training text. At inference, the token at the end of our question already carries, from attention, something like “the winner of the 2026 FIFA World Cup is being asked for”. Multiplying by Wu lights the Spain detector up at 0.93, while detectors for things like capital cities or food stay small and get silenced. Multiplying by Wd turns the surviving strengths into a change of direction, and adding it on moves the token from [0.10, 0.20] to [0.77, 0.94], toward Spain, pulled straight from the model’s weights, with no help from the sentence itself.

The same honest caveat applies as with the “desert” and “living” dimensions earlier: “one detector equals one fact” is a way to build intuition, not the literal truth, because in a real model a single fact is smeared across many patterns and a single pattern takes part in many facts. The silencing is simplified too: Qwen3.8-27B uses a third matrix, a gate, and a smooth function called SiLU instead of a hard cut-off, so weak matches fade out rather than switch off. The idea is the same.

feed-forward

Feed-forward: Wu, silence the weak ones, Wd, add.

The feed-forward flow with real shapes A left-to-right flow. The token vector x with 5,120 numbers is multiplied by Wu, the up_proj matrix of layer 3, and becomes a much wider vector of 17,408 numbers. The weak numbers are silenced. The survivors are multiplied by Wd, the down_proj matrix, and become a vector of 5,120 numbers again. That change is added to the original x, which travels along the top, giving the new vector. Each of the five parts lights up in turn. one token's vector Wu asks 17,408 questions at once the weak answers are silenced Wd turns the survivors into a change the change is added to the original x
The feed-forward step: Wu asks 17,408 questions at once, the weak answers are silenced, and Wd turns the strong ones into a change that is added back onto the token. (Real shapes. Qwen3.8-27B also has a gate_proj and a smooth SiLU, folded here into one silencing step.)

Put together, here is how a token’s vector moves through the feed-forward step. First, we multiply the token’s vector, the one attention has just updated, by Wu; out comes an array of 17,408 numbers, one score per detector. Second, we silence the weak ones. Third, we multiply what’s left by Wd; out comes an array of 5,120 numbers, the same width as the token, and we add it straight onto the original token. That’s the entire feed-forward step.

feed-forward

Attention reads the sentence. Feed-forward recalls what training stored.

Attention versus feed-forward Two panels. The left panel, attention, shows the tokens of the question with arcs of different thickness carrying influence from the other tokens into the question mark; its caption reads context from this sentence. The right panel, feed-forward, shows one token vector passing a bank of detectors and being moved toward Spain in the idea plane; its caption reads knowledge from training. context from this sentence knowledge from training
Attention is the only place tokens talk to each other: context from this sentence. Feed-forward works on one token at a time, with knowledge stored during training.

It’s worth pinning down the difference between these two steps, because they’re easy to blur together. Attention is the one and only place where tokens look at each other; it’s about this specific sentence, which token is shaping which. The feed-forward step runs on each token on its own, with the same numbers applied at every position; it’s about everything the model memorized across all of its training. One handles the current sentence, the other handles stored knowledge.

Layers

layers

Attention + feed-forward = one layer.

One layer: attention then feed-forward, each adding a change to the stream The token vector x travels along a stream from left to right. Attention takes a copy off the stream and puts its change back with a plus. Then feed-forward does the same. Both changes are added onto the same stream. A frame around the pair is labelled one layer, and the stream carries on out of the layer.

layers

Stack many layers: 64 in Qwen3.8-27B.

The token vector flows down a stack of 64 layers On the left a tall stack of layer slabs: layers 1 to 4, a gap marked 57 more, and layers 62 to 64, with a brace labelled 64 layers. A small vector strip flows down beside the stack and lights each slab it passes. On the right, an idea plane with the axes a country and football shows the last token's vector as an arrow that changes direction layer by layer, leaving a dotted trail, and ends up pointing at Spain. At the bottom the final vector is ringed with the caption now carries the context.
One layer is attention plus feed-forward, and each only adds a change to the token's vector; the vector itself is never replaced. (Real layers also normalise the vector before each step.)

So far I’ve described one attention step followed by one feed-forward step. Together, these form a single layer, or block, and notice that neither step replaces the token’s vector: each one only adds a change onto it. A real model doesn’t have just one layer; it stacks many of them, and the count sits in config.json as num_hidden_layers: 64 for Qwen3.8-27B. A few things are worth knowing about the stack. Each layer has its own Wq, Wk, Wv, Wu and Wd; they’re not shared between layers, which is where most of those 27 billion numbers actually live, the per-layer count multiplied by the number of layers. Each layer works on the output of the layer before it: a token’s vector flows straight down the stack, and every layer reads the current vector, mixes in context again with attention, recalls and transforms again with the feed-forward step, and adds its changes back. Roughly speaking, earlier layers settle simpler, more local structure, and later layers build up more abstract meaning, though that’s a tendency rather than a strict rule. After the final layer, the vector sitting at the last position, the one belonging to the last token of our input, is the vector we read the prediction from.

Picking the next token

predicting the token

After the last layer, the final token carries the context.

One vector per token after the last layer The sentence The cat sat on the as five chips, each with a column of numbers under it. Only the column under the last token is highlighted; it is then lifted out and enlarged, because that vector is the one that predicts the next token. one vector per token this one predicts the next token 5,120 numbers

predicting the token

Dot product with every token in the vocabulary.

The last vector is dotted with every token of the vocabulary On the left the last token's vector as a column of numbers. On the right a long scrolling list of real vocabulary tokens, each with its id. A scan line runs down the list and a score appears for every token. The five candidate words mat, rug, floor, chair and table have the biggest scores; they then rise to the top of the list, sorted. last vector 5,120 numbers id token score + 248,315 more, all lower 248,320 scores in all: the logits lm_head.weight [248,320 x 5,120]
After the last layer, the vector at the final position has soaked up everything needed to predict the next token.

Which brings us to the final step: turning that one vector into an actual next token. First, we score every token in the vocabulary. We take the last vector in the sequence and, for each token in the vocabulary, compute the dot product between the last vector and that token’s own vector. Each dot product is a single number, a score, and there is one score per vocabulary token, 248,320 of them. These scores are called logits. The token vectors we score against are the rows of an output table, lm_head. In some models that table is simply the embedding table from the very start, reused in reverse, and tie_word_embeddings in config.json tells you which: for Qwen3.8-27B it’s false, so the output table is a separate set of numbers, trained for scoring.

predicting the token

Divide the scores by the temperature.

Dividing the scores by the temperature Five bars show the scores of mat, rug, floor, chair and table divided by the temperature T, next to a dial that shows T. At T equal to 1 the scores are 2.00, 1.40, 1.10, 0.60 and 0.30. At T equal to 0.5 the bars are twice as tall and the gaps between them double. At T equal to 2 they are half as tall and squeeze together. gap 1.70 score ÷ T 0.5 1 2 T = 1.0

predicting the token

Softmax turns the scores into probabilities between 0 and 1.

Softmax turns five scores into probabilities that sum to one Five score bars for mat, rug, floor, chair and table (2.00, 1.40, 1.10, 0.60, 0.30) change into the probabilities 0.419, 0.230, 0.170, 0.103 and 0.077, then stack into a single column whose total height is exactly 1.00. scores probabilities 0 1 sum 1.00

predicting the token

Top-k sampling: pick one of the k most likely tokens.

Top-k sampling with k equal to 3 Five probability bars for mat, rug, floor, chair and table: 0.419, 0.230, 0.170, 0.103 and 0.077. Only the three most likely stay lit and their probabilities are renormalised to 0.51, 0.28 and 0.21. A die is rolled, picks mat, and a mat chip flies to the end of the sentence The cat sat on the. k = 3 mat
Temperature divides every score before the softmax. Below 1 the gaps stretch and the top word wins by more; above 1 they squeeze together and the choice flattens.

Now we have a score for every possible next token, but scores are not probabilities, and two steps fix that. First, temperature: we divide every score by a number T, the temperature. If T is less than 1, the gaps between scores grow, which makes the model more decisive and more repetitive; if T is greater than 1, the gaps shrink, which makes it more varied and more random; and as T approaches 0, the model simply always takes the top-scoring token. Second, softmax, the same normalizing step we met inside attention: it turns the temperature-adjusted scores into proper probabilities, all positive and adding up to 1. With scores of 2.0 for “mat”, 1.4 for “rug”, 1.1 for “floor”, 0.6 for “chair” and 0.3 for “table”, softmax at T = 1 gives mat 0.419, rug 0.230, floor 0.170, chair 0.103 and table 0.077.

Finally, we sample: we draw one token at random using those probabilities as weights, so a token with probability 0.42 is picked about 42% of the time. That randomness is why the same prompt can give different answers. A few common variations sit on top of this. Greedy skips the randomness and always takes the highest-scoring token. Top-k keeps only the k highest-probability tokens and samples from just those: with k = 3 we keep mat, rug and floor, rescale them to 0.51, 0.28 and 0.21, and roll. Top-p sorts tokens by probability, keeps the smallest group whose probabilities add up to at least p, and samples from just those: with p = 0.8, mat and rug only reach 0.649, adding floor takes us to 0.819, and we end up with the same three.

Closing the loop

predicting the token

Append the token. Run it all again.

One full pass of the loop The sentence The cat sat on the goes into the embedder, then through attention plus feed-forward repeated 64 times, then into sampling. Sampling produces the token mat, which drops onto the end of the sentence. The longer sentence is then fed in again.
One forward pass: the embedder, 64 layers of attention and feed-forward, then sampling. The chosen token is appended and the whole stack runs again for the next one. (In practice the model caches work from earlier tokens; part 2 covers that.)

Then we do the only thing left: append the token we picked to the input, and run the whole model again for the next one. That is the loop from the very beginning of this guide, now with every step underneath it filled in: text becomes tokens, tokens become embeddings, attention lets them influence each other, the feed-forward step folds in learned facts, a stack of 64 of these layers refines the result, and a final scoring-and-sampling step picks the next token, on repeat.

training

Training runs the very same pass, then sends the error back.

One training step through the whole model The training sentence The cat sat on the runs through the embedder, attention plus feed-forward repeated 64 times, and sampling. The model guesses dog where the training data says mat, so the error is large. The error flows back through every stage and nudges every number. At inference the same pass runs with every number frozen, and the trained model now says mat. embedder attention + feed-forward × 64 layers sampling The cat sat on the ? guess dog, training data says mat: error large the error flows back and nudges every number inference: the same pass, every number frozen
Training runs this very same pass. The guess is scored against the real next word, and the error flows back to nudge every number in every stage. Inference is the same pass with the numbers frozen.

Now step back and notice one thing about all of this. Everything we’ve walked through, the embedding lookup, then attention and the feed-forward step repeated across every layer, then the final scoring, is the structure of the transformer. And here’s the point I want to land: this exact same structure is what runs during training. There’s no separate training-time machine. At the start, every number in it is random: the embedding table, the Wq, Wk, Wv, Wu and Wd in each layer, and the output table. We run this structure forward on a piece of training text to predict the next token, we measure how far off that prediction is from the token that actually came next, and we push that single “how off we are” number back through the whole structure, nudging every number in it in proportion to how much it contributed to the error. That’s gradient descent, the same mechanism I described for the embeddings, except now you can see it’s tuning everything at once, in one pass, repeated over and over across enormous amounts of text. Inference, the thing that actually serves your request, is just this same forward pass with all of those numbers now frozen in place.

And look at what it was all built from: vectors holding meaning, matrices transforming them, dot products measuring how related they are, a softmax turning scores into weights and odds, and a silencing step switching off weak matches. The three tools from the start of this article, repeated at enormous scale, are the whole trick.

And this is exactly why an LLM is nothing like the microservice it resembles from the outside. A single response is not one cheap request; it’s this entire stack run once per token, looping until the model decides to stop. That one fact, that the unit of work is a token and not a request, is what reshapes everything an SRE cares about: latency, throughput, batching, memory, and cost. Part 2 picks up from here: how a raw base model is turned into one that follows instructions and reasons, and the GPU, memory, and quantization mechanics underneath serving it.

Comments & reactions

React or leave a comment below — sign in with GitHub. It all lives in this site's GitHub Discussions.