Skip to main content
Xenors Xenors
Home › Data Science › Transformer Architecture and the Self-Attention Mechanism

Module 10: Generative AI, LLMs & Prompt EngineeringBy Ashok Kumar Yadav4 min read

Transformer Architecture and the Self-Attention Mechanism

Every modern large language model is built from the Transformer. Its core idea is simple: let every token look at every other token and decide how much each one matters.

Transformer encoder and decoder diagram with self-attention heads and positional encoding
Quick answer: Self-attention computes softmax(QK^T / sqrt(d_k)) V. Each token produces a query, key and value, scores its query against all keys, and takes a weighted sum of values. Multi-head attention runs this several times in parallel.

What problem Transformers solved

RNNs read tokens one at a time, which is slow to train and forgets distant context. Transformers process the whole sequence in parallel and connect any two positions with one attention step, so training scales on GPUs and long-range relationships are easy to learn.

ComponentRole
Token embeddingTurns ids into vectors
Positional encodingInjects word order
Self-attentionMixes information across tokens
Feed-forward layerTransforms each token independently
Residual + LayerNormKeeps training stable

Self-attention from scratch

Queries ask what to look for, keys advertise what each token contains, and values carry the information. Dividing by the square root of the key size keeps the softmax from saturating.

Scaled dot-product attention
import torch, math
import torch.nn.functional as F

def attention(q, k, v, mask=None):
    scores = q @ k.transpose(-2, -1) / math.sqrt(q.size(-1))
    if mask is not None:
        scores = scores.masked_fill(mask == 0, float("-inf"))
    weights = F.softmax(scores, dim=-1)
    return weights @ v, weights

x = torch.randn(1, 4, 8)                  # batch=1, 4 tokens, dim 8
Wq, Wk, Wv = (torch.randn(8, 8) for _ in range(3))
out, w = attention(x @ Wq, x @ Wk, x @ Wv)
print(out.shape, w.sum(-1))               # each row of weights sums to 1

Multi-head attention

Several smaller attention heads let the model track different relationships at once, such as syntax in one head and coreference in another. Outputs are concatenated and projected.

Causal multi-head self-attention
import torch.nn as nn

class MultiHead(nn.Module):
    def __init__(self, d=64, heads=4):
        super().__init__()
        assert d % heads == 0
        self.h, self.dk = heads, d // heads
        self.qkv = nn.Linear(d, 3 * d)
        self.out = nn.Linear(d, d)
    def forward(self, x, causal=False):
        B, T, D = x.shape
        q, k, v = self.qkv(x).chunk(3, dim=-1)
        q, k, v = (t.view(B, T, self.h, self.dk).transpose(1, 2) for t in (q, k, v))
        mask = torch.tril(torch.ones(T, T)) if causal else None
        y, _ = attention(q, k, v, mask)
        return self.out(y.transpose(1, 2).reshape(B, T, D))

print(MultiHead()(torch.randn(2, 10, 64), causal=True).shape)

Positional encoding and the full block

Attention alone ignores order, so a position signal is added to the embeddings. The original paper used fixed sine and cosine waves, and many modern models learn positions or use rotary embeddings. A Transformer block wraps attention and a feed-forward network with residual connections and layer normalisation.

Practical tipEncoder-only models such as BERT suit understanding tasks, decoder-only models such as GPT suit generation, and encoder-decoder models suit translation and summarisation.

Hands-on practice

Run the examples in the downloadable Jupyter notebook. The extra checks below focus on mistakes that appear in real projects, not just syntax.

Calculate scaled dot-product attention

Practice example
import torch, math
Q=torch.tensor([[1.,0.],[0.,1.]])
K=Q.clone(); V=torch.tensor([[10.,0.],[0.,20.]])
W=torch.softmax(Q@K.T/math.sqrt(2),dim=-1)
print(W.round(decimals=3)); print((W@V).round(decimals=2))
tensor([[0.670, 0.330], [0.330, 0.670]])
...

Scaling prevents dot products from growing too large as key dimension increases.

Verify causal masking

Practice example
scores=torch.zeros(4,4)
mask=torch.triu(torch.ones(4,4),diagonal=1).bool()
scores=scores.masked_fill(mask,float('-inf'))
print(scores)
upper triangle becomes -inf

A causal mask prevents a decoder token from reading future tokens.

Self-check

  • Can you explain why the first practice result is trustworthy rather than just plausible?
  • What would you monitor or test before using this Transformers & Attention workflow in production?
  • Which assumption in the example would break first on a larger or messier dataset?

Frequently asked questions

What is self-attention in simple words?

Each word compares itself with every other word in the sentence and builds a new representation as a weighted mix of them.

Why divide by the square root of d_k?

Large dot products push softmax into regions with tiny gradients. Scaling keeps the values in a healthy range.

Why do Transformers need positional encoding?

Attention treats tokens as an unordered set, so position information must be added explicitly.

Primary references

Use these official sources when exact syntax, defaults or definitions matter beyond the examples here.

Written by Ashok Kumar Yadav
Ashok Kumar Yadav writes Xenors tutorials on applied AI, programming and data workflows. Every lesson is built around code you can run and check yourself.
GitHub · LinkedIn

Continue exploring