Part 6 of: Transformer from Scratch in Pure C
Goal: Implement the full "Attention is All You Need" paper in pure C, building up from primitives to a complete machine translation pipeline.
attentionimplements the core innovation of the Transformer: scaled dot-product attention. It computes context vectors by letting each token dynamically decide which other tokens are important, without using recurrence or convolutions. Built on top of mini-tensor.
- Pure C (C99), depends only on
mini-tensor - Faithful to the paper — uses continuous, learned linear projections (
Parameter), completely independent from the neural network module'sLinearlayer - Clean mathematical separation — internal static helpers handle queries, keys, values, scoring, scaling, and softmax without exposing internal state
- Forward propagation only — cleanly separates the forward algorithm from the immense complexity of attention backpropagation
- Zero parameter bloat — no output projection (
W_O) yet, teaching the pure attention mechanics before multi-head concatenation - 6-test suite with deep identity validation (Scores approaching Identity Matrix) and analytical scaling verification
- Built on mini-tensor ← | Foundation for multi-head-attention →
Where this project fits in the complete Transformer pipeline:
Token
│
Embedding
│
Positional Encoding
│
Attention ← This project (Part 06)
│
Multi-Head Attention
│
LayerNorm
│
Feed Forward
│
Encoder Block
Previous modules transformed discrete tokens into continuous embeddings (Part 4) and injected position awareness (Part 5). However, these representations are static—the embedding for "bank" is identical whether the context is "river bank" or "bank account".
Attention solves this context problem. It provides a mechanism for tokens to exchange information:
- Each token formulates a Query ("What information do I need?").
- Each token advertises a Key ("What information do I contain?").
- Each token holds a Value ("What information will I pass along?").
By computing the dot product of every Query against every Key, the network dynamically assigns a Weight to every relationship, producing a contextualized output representation.
A single public header (include/attention.h) exposes:
Attention— holds the three learned projection matrices (W_q,W_k,W_v)attention_create()— allocates the struct and initializes the projections via Xavierattention_forward()— computes the full scaled dot-product attentionattention_free()— releases all memory
Position-aware inputs (from Part 5)
│
├─► X × W_Q ─► Queries (Q) ─┐
│ │
├─► X × W_K ─► Keys (K) ────┴─► Scores (Q·Kᵀ)
│ │
│ ▼
│ Scale (1/√d_k)
│ │
│ ▼
│ Weights (Softmax)
│ │
├─► X × W_V ─► Values (V) ────────┘
│
▼
Context Output
| Future Part | What this project provides |
|---|---|
| Part 7 — Multi-Head Attention | Multiple copies of this exact logic executed in parallel, concatenated, and projected through W_O |
| Part 10 — Encoder Layer | Multi-Head Attention wrapped with residual connections and LayerNorm |
| Part 12 — Decoder Layer | Adds Masked Attention (preventing looking into the future) and Cross Attention |
The paper defines scaled dot-product attention as:
Attention(Q, K, V) = softmax( (Q Kᵀ) / √d_k ) V
Where:
Q = X W_QK = X W_KV = X W_V
| Matrix | Shape |
|---|---|
Input X |
seq_len × d_model |
WQ, WK, WV |
d_model × d_k |
Q, K, V |
seq_len × d_k |
Scores, Weights |
seq_len × seq_len |
Output |
seq_len × d_k |
attention/
├── include/
│ └── attention.h # Public API (single include)
├── src/
│ ├── attention.c # attention_create, forward, free, static helpers
│ └── demo.c # demo printing step-by-step intermediate matrices
├── tests/
│ └── test_attention.c # 6-test suite — make test
├── Makefile
├── README.md
└── Analysis.md
- GCC (C99 compatible)
- GNU Make
libm(math library)mini_tensorsource at../mini-tensor/
makemake testmake demomake valgrindmake cleanWhile a multi-layer perceptron's Linear layer (with bias vectors and broadcasting) works mathematically, Queries, Keys, and Values are purely learned linear projections. The original paper does not define them as neural networks. Utilizing raw Parameter* matrices aligns strictly with the conceptual model of the paper and eliminates unnecessary bias addition overhead.
The output projection naturally belongs to Multi-Head Attention (Part 07). Incorporating it here teaches the wrong abstraction. Single-head attention's final state is simply the context vector (Weights × V).
Computing queries, computing scores, and scaling are isolated into distinct helper functions to enhance mathematical clarity and debugging. However, they remain static to maintain an intentionally tiny public API.
typedef struct {
Parameter *W_q;
Parameter *W_k;
Parameter *W_v;
size_t d_model;
size_t d_k;
} Attention;
Attention* attention_create(size_t d_model, size_t d_k);
Matrix* attention_forward(Attention *attn, const Matrix *input);
void attention_free(Attention *attn);#include "attention.h"
int main(void) {
/* Create Attention Layer */
size_t d_model = 512;
size_t d_k = 64;
Attention *attn = attention_create(d_model, d_k);
/* Construct input matrix (seq_len=10, d_model=512) */
Matrix *input = /* ... position-aware embeddings ... */ NULL;
/* Forward pass through attention mechanism */
Matrix *output = attention_forward(attn, input); /* (10 × 64) */
matrix_free(output);
attention_free(attn);
return 0;
}=== Attention Test Suite ===
Running test_shape...
Pass.
Running test_attention_score_shape...
Pass.
Running test_softmax_rows_sum_to_one...
Pass.
Running test_analytical_scaling...
Pass.
Running test_identity_case...
Pass.
Running test_attention_weights_identity...
Pass.
All tests passed!
| Test | What it validates |
|---|---|
test_shape |
(3 × 8) goes in, (3 × 8) comes out |
test_attention_score_shape |
Scores matrix is precisely (3 × 3) given seq_len=3 |
test_softmax_rows_sum_to_one |
Ensures probability distribution normalization across seq_len |
test_analytical_scaling |
Computes scaling on a known minimal matrix vs analytical derivation |
test_identity_case |
Force-sets W_q = W_k to generate symmetric Scores == Scores^T |
test_attention_weights_identity |
Evaluates deterministic Softmax forcing an identity matrix, proving Output ≈ V |
| Missing feature | Why | Where it belongs |
|---|---|---|
| Multi-Head implementation | Introduces concatenation complexity; better kept separate | Part 7 — Multi-Head Attention |
| Backpropagation | Highly complex derivatives via Jacobian matrices | Future Part |
| Masking | Only needed for the Decoder | Part 12 — Decoder Layer |
- The project compiles cleanly under strict GCC flags (
-Wall -Wextra -Werror -pedantic). - Only
mini-tensorsources are compiled. It deliberately decouples fromneural-netto prove attention is a primitive mathematical operation.
Engineering Design © 2026 Shahid Ul Islam.
Built with passion for Mathematical Rigour and Technical Excellence.