Concepts / Attention Scores and Weights

Attention Scores and Weights

The SelfAttention_v1 class stores three trainable matrices, W_query, W_key, and W_value, each mapping the input dimension to the output dimension.

  • Programming

Predicting the Output Before Reading the Code

A self-attention class can look correct line by line and still produce a result you did not expect. The fastest way to catch that problem is to form a prediction first and then check it against what the code actually does. This article builds that habit using a compact self-attention class written as a subclass of nn.Module.

Start with the input. The input is a matrix with one row per token, and each row is an embedding vector. Before reading any internals, decide what the output should look like. Ask how many rows the output has, and what determines how many columns it has. Those two questions are the foundation of every check that follows.

Two Meanings of Weight

Many learners meet the word 'weight' for the first time in a neural network course and assume it always means the same thing: a number that training adjusts until the model works. That assumption is reasonable, but once attention enters the picture it becomes a source of confusion. The word appears in two places that play very different roles. One place is the set of weight matrices that a self-attention layer learns. The other is the attention weights that the mechanism computes while it runs.

Definition

Weight parameters are the learned coefficients of a neural network. Training optimizes them, and once training has set them they describe how the network connects its inputs to its outputs.

Definition

Attention weights are the values that determine the extent to which a context vector depends on the different parts of the input. They express how strongly the network focuses on each input element when it builds the context vector for one query. Because they are computed fresh for each query, they are dynamic and context-specific rather than fixed coefficients.

The clean way to keep the two apart is to ask when each value is determined. Weight parameters are settled by training and then reused. Attention weights are produced during a forward computation, so the same weight matrix can yield different attention weights for different queries. The diagram below places the two side by side.

W_queryset by trainingattention weightsrecomputed for each querycontext vectordepends on the weightsW_keyset by trainingW_valueset by training
How do the fixed trainable matrices differ from the context-dependent attention weights that are recalculated on every forward pass?

What the Class Stores in Its Constructor

The class SelfAttention_v1 inherits from nn.Module. The source describes this base class as a fundamental building block of PyTorch models that supplies the functionality needed to create and manage model layers. Inheriting from it is what lets the class hold trainable parameters in a way PyTorch can track.

Inside the constructor, three trainable weight matrices are created: W_query, W_key, and W_value. Each one is wrapped in nn.Parameter and sized so that it maps the input dimension d_in to the output dimension d_out. The three matrices start from random values, and the same input vectors are multiplied by each one to produce three different views of the data.

The constructor only sets up the starting values. As the model is exposed to more data during training, it adjusts these trainable weights. Learning changes them later, which is why they are called trainable. Nothing in the forward method changes the matrices themselves; it only reads them.

Tracing the Forward Method

The forward method is where the input actually flows through the mechanism. It takes the input matrix x, projects it into keys, queries, and values, computes a score for every pair of tokens, converts those scores into weights, and then uses the weights to combine the values. Read the method as a pipeline with five stages: projection, scores, scaled softmax weights, and context vectors.

Input X6 x 3Queries6 x 2Scores6 x 6Attention weights6 x 6Context vectors Z6 x 2Keys6 x 2Values6 x 2times W_querytimes W_keytimes W_valuequeries times keys transposedkeysscale then softmaxweights times valuesvalues
What shape does each intermediate tensor have at each step, and how do the input and weight matrix dimensions determine those shapes?

Read each arrow as one matrix operation. The input X is multiplied by three weight matrices to produce queries, keys, and values. Queries and keys are combined into the attention score matrix. Scores are scaled and normalized into attention weights, and those weights are then applied to the values to produce the context vectors Z. The numbers shown in the shape labels are the ones that matter here; the source's figure also notes that its attention weight values are placed for visual consistency rather than being real weights, so the structure is what you should memorize.

Worked example

Predicting every shape from the input and weight sizes

The input X has six tokens with an input dimension d_in of 3. The output dimension d_out is 2. What is the shape of each intermediate tensor and of the final context matrix?

  1. 1
    Read the input shape

    X has one row per token, so it is 6 x 3.

  2. 2
    Project through each weight matrix

    Each weight matrix maps d_in = 3 to d_out = 2, so it has shape 3 x 2. Multiplying a 6 x 3 matrix by a 3 x 2 matrix gives 6 x 2. Queries, keys, and values are each 6 x 2.

  3. 3
    Compute the score matrix

    Queries (6 x 2) times the transposed keys (2 x 6) gives a 6 x 6 matrix. Each row corresponds to one query and each column to one key.

  4. 4
    Apply softmax to get weights

    Scaling and softmax keep the shape, so the attention weights are also 6 x 6.

  5. 5
    Combine with values

    Attention weights (6 x 6) times values (6 x 2) gives context vectors of shape 6 x 2.

Result

The context matrix Z has 6 rows, one per input token, and 2 columns, set by d_out.

Notice what this trace reveals. The number of rows in the output comes from the number of input tokens, and the number of columns comes from d_out, the output dimension chosen for the weight matrices. Context vectors have one row per input token and d_out columns, which makes the output shape a useful first check against what you expected.

Dot Products as Unnormalized Scores

The second stage computes attention scores. An attention score is a dot product between a query vector and a key vector, and it is a raw, unnormalized number. This is the same kind of dot-product computation used in simplified self-attention, with one new aspect: the query and the key are not the raw input elements. They are obtained by transforming the inputs through their respective weight matrices.

Index positions matter here. The source computes the score for the second input token first. The query comes from that second token and is paired with the key at index 1. Python starts indexing at 0, so the second key is keys[1], not keys[2]. The score is the dot product of the two vectors, giving one number that measures how well that query matches that key.

Rather than repeating that dot product for every key, the same result can be obtained for all keys at once by multiplying the query by the transposed key matrix. The entry at index 1 of the resulting row matches the single score computed earlier, which is a useful quick check when you write this code yourself. The diagram below shows how the matrix is laid out: each row belongs to one query and each column to one key.

score[1,0]query 1 with key 0score[1,1]query 1 with key 1score[1,2]query 1 with key 2score[1,3]query 1 with key 3score[1,4]query 1 with key 4score[1,5]query 1 with key 5
Which query and which key produce the score in each column, and why does one query row hold one score per key?

The highlighted cell is the score for the second query, which in zero-based Python indexing is row 1, paired with key 1. Because there are six queries and six keys, the full score matrix has one row per query and one column per key.

From Scores to Weights: Scaling and Softmax

Attention scores are not yet attention weights. The next step converts the scores by scaling them and then applying softmax. Scaling divides each score by the square root of the embedding dimension of the keys. Taking a square root is the same as raising to the power 0.5, so the scaling can be written as a single equation.

Rule

Formula, step by step
  1. 1
    omega_i is the unnormalized attention score for key i, the raw dot product between the query and that key.
  2. 2
    d_k is the embedding dimension of the keys, read from the shape of the key matrix.
  3. 3
    Dividing by the square root of d_k scales each score before normalization.
  4. 4
    Softmax converts the scaled scores into positive values that add up to one along the chosen dimension.
  5. 5
    w_i is the resulting attention weight for that key.

The source code performs the same steps. It reads the key dimension from the shape of the keys, divides the scores by its square root, and applies softmax along the last dimension. Softmax is what turns the scaled scores into the weights. Its outputs are positive and sum to one across the dimension it is applied to, so the attention weights for one query form a distribution over the input tokens. Larger weights mean the context vector leans more heavily on those tokens.

Mistakes That Hide Divergence

When a result looks wrong, the cause often sits in a step that seems routine. The following mistakes are the ones most likely to produce a value that differs from what you expected.

  • Common mistake

    Treating the weight matrices and the attention weights as the same thing

    What it looks like

    Assuming that changing W_query during a forward pass is how the attention weights for a new query get computed

    Why it fails

    W_query is a learned parameter set by training, while attention weights are recomputed from context on every forward pass

    Do this instead

    Keep the two apart by asking when each value is determined: training fixes the matrices, and the forward computation produces the weights

  • Common mistake

    Using one-based indexing for keys

    What it looks like

    Pairing the second query with keys[2] instead of keys[1]

    Why it fails

    Python starts indexing at 0, so the second key is keys[1]

    Do this instead

    Check the key index first when a score looks off, and confirm it against the matching row of the transposed key matrix

  • Common mistake

    Skipping the scaling step

    What it looks like

    Applying softmax directly to the raw dot products

    Why it fails

    The scores are meant to be divided by the square root of the key embedding dimension before softmax, so skipping it changes every weight

    Do this instead

    Verify that the scores were divided by the square root of d_k before normalization

  • Common mistake

    Normalizing along the wrong axis

    What it looks like

    Running softmax over the query axis instead of the key axis

    Why it fails

    Each query's weights must form a distribution over the input tokens, which is a normalization across keys

    Do this instead

    Confirm that each row of the attention weight matrix sums to 1

  • Common mistake

    Assuming a correct-looking line of code is correct in context, without checking the shape or the row-by-row output

If the rows do not match, the mismatch tells you which part of the pipeline to examine. Because the forward method is a fixed sequence of matrix operations, you can check its output at each stage rather than only at the end.

Locating Where a Value First Diverges

The source offers a simple verification. In the output matrix, the second row should match the context vector computed for the second input token in the single-token computation. Matching that row is a strong check, because it confirms that the batched matrix version and the single-token computation agree.

When a score or weight differs from the expected value, work through a fixed checklist in this order: the query source, the key index (remembering that Python indexes from 0), the scaling step, and the softmax axis. Stop at the first checkpoint that fails. That step is where the value first diverges.

What do you think happens?

Six input tokens have d_in = 3 and d_out = 2. Before reading further, what shape does the context matrix Z have?

Reveal answer
Answer:

6 rows by 2 columns

There is one row per input token, so 6 rows. The columns are set by d_out, which is 2.

MEDIUM

A colleague's self-attention class returns context vectors whose second row does not match the single-token computation for the second input token. Describe the order in which you would check the query source, key index, scaling, and softmax axis, and state what each check would confirm.

Hints
  • Start by confirming the shape of each intermediate tensor before reading any numbers
  • Remember that the second key is at index 1 in Python
  • Each query's attention weights should sum to 1 across the key axis

Key Takeaways

  • The forward method projects the input through W_query, W_key, and W_value, computes scores, scales and applies softmax to get weights, and combines the weights with the values to produce context vectors.
  • Weight parameters are learned during training and then fixed for use, while attention weights are computed fresh for each query from context.
  • An attention score is an unnormalized dot product between a query and a key, scaled by the square root of the key dimension and normalized by softmax along the key axis.
  • When a result diverges, check shapes first, then the query source, key index, scaling step, and softmax axis, in that order, to find the first failing step.

Related concepts

More concepts starting with A