Build the mental model
Before attention, sequence models built on RNNs (or LSTMs/GRUs) had to read an entire input sequence and compress everything it contains into one fixed-size hidden state vector before producing any output. This is a severe information bottleneck: for a long sentence or document, whatever the model learned from the first few tokens has to survive being overwritten step after step by every token that follows, so by the time the model reaches the end, early context is diluted or forgotten entirely. Attention removes this bottleneck by letting the model, at each output step, look back directly at every position of the input rather than relying on a single squeezed-through summary. It does this with three learned projections: a query vector representing what the current step is looking for, and a key vector and value vector for every input position. The query is compared against every key (typically via dot product) to produce a relevance score per position; softmax turns these scores into attention weights that sum to one, and the output is the weighted sum of the value vectors. Distance in the sequence no longer matters — position 1 is exactly as reachable as position 1000.
Connect it to a real scenario
On Thuta's Tutorial Platform, attention is exactly the mechanism a learned lesson-recommendation model would need to decide which of a user's past lessons are actually relevant to recommending the next one — instead of squashing the whole learning history into one summary vector the way an RNN would. A query built from 'what topic is the user on now' attends over keys built from every past lesson the user completed, giving directly related lessons a high weight and unrelated ones near zero, no matter how many lessons or how much time separates them, since attention has no built-in decay based on position or recency.
Try the working example
import torch
import torch.nn.functional as F
torch.manual_seed(0)
seq_len, d_k = 4, 8
Q = torch.randn(seq_len, d_k)
K = torch.randn(seq_len, d_k)
V = torch.randn(seq_len, d_k)
scores = Q @ K.T / (d_k ** 0.5)
weights = F.softmax(scores, dim=-1)
output = weights @ V
print("attention weights shape:", weights.shape)
print("output shape:", output.shape)
print("weights row sums:", weights.sum(dim=-1))attention weights shape: torch.Size([4, 4])
output shape: torch.Size([4, 8])
weights row sums: tensor([1.0000, 1.0000, 1.0000, 1.0000])
The weights matrix shows how much each of the 4 query positions attends to each of the 4 key positions, and every row sums to 1 because of softmax. The output shape matches the (seq_len, d_k) shape of Q and V.5-minute try-it
Change seq_len from 4 to 10 and rerun — how does the attention weights matrix shape change? Then increase d_k from 8 to 64 and remove the sqrt(d_k) scaling factor — observe what happens to the softmax output and why the scaling matters.
One important caution
Skipping the sqrt(d_k) scaling means dot product scores grow with d_k, pushing softmax into saturation and causing vanishing gradients during training
It's easy to confuse the attention weights shape with the output shape — weights have shape (query_len, key_len) while the output has shape (query_len, value_dim), and these can differ when queries and values have different lengths or dimensions
Wikipedia — Attention (machine learning) — Deep Learning