Level 5 — MasteryTransformer Architecture

Transformer Architecture

90 minutes60 marksprintable — key stays hidden on paper

Time limit: 90 minutes Total marks: 60 Instructions: Answer all three questions. Show full derivations. Code may be written in Python/NumPy pseudocode but must be dimensionally correct.


Question 1 — Scaled Dot-Product Attention: Scaling, Gradients, Complexity (22 marks)

Consider single-head scaled dot-product attention over a sequence of length nn with model dimension dkd_k:

Attn(Q,K,V)=softmax ⁣(QKdk)V\text{Attn}(Q,K,V) = \text{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V

where Q,KRn×dkQ,K \in \mathbb{R}^{n\times d_k}, VRn×dvV \in \mathbb{R}^{n\times d_v}.

(a) Assume each component of the query and key vectors is drawn i.i.d. with mean 00 and variance 11, and queries and keys are independent. Compute the mean and variance of a raw dot-product score sij=qikj=m=1dkqimkjms_{ij} = q_i \cdot k_j = \sum_{m=1}^{d_k} q_{im}k_{jm}. Hence justify why the 1dk\frac{1}{\sqrt{d_k}} factor (rather than 1dk\frac{1}{d_k}) is the correct normalisation. (6)

(b) Let a=softmax(z)a = \text{softmax}(z) where zRnz \in \mathbb{R}^n. Derive the Jacobian aizj\frac{\partial a_i}{\partial z_j} in closed form. Then explain, using the magnitude of this Jacobian, the vanishing-gradient failure mode that the scaling factor mitigates when dkd_k is large. (8)

(c) Give the time and memory complexity of computing the full attention matrix for one head, as functions of nn, dkd_k, dvd_v. State separately the cost of the QKQK^\top product, the softmax, and the multiplication by VV. (4)

(d) FlashAttention achieves the same output with O(n)O(n) memory (excluding inputs/outputs) instead of O(n2)O(n^2). State the single algorithmic idea that makes this possible and name the numerical technique used to keep the streaming softmax stable. (4)


Question 2 — Multi-Head Attention & Positional Encoding (20 marks)

(a) A multi-head attention layer has hh heads, model dimension dmodeld_{\text{model}}, and per-head dimension dk=dv=dmodel/hd_k = d_v = d_{\text{model}}/h. Count the total number of parameters (weights only, no biases) in the four projection matrices WQ,WK,WV,WOW^Q, W^K, W^V, W^O. Show that this count is independent of hh and equals 4dmodel24\,d_{\text{model}}^2. (6)

(b) The sinusoidal positional encoding is PE(pos,2i)=sin ⁣(pos100002i/d),PE(pos,2i+1)=cos ⁣(pos100002i/d).PE_{(pos,2i)} = \sin\!\left(\frac{pos}{10000^{2i/d}}\right),\qquad PE_{(pos,2i+1)} = \cos\!\left(\frac{pos}{10000^{2i/d}}\right). Prove that for any fixed offset kk, PEpos+kPE_{pos+k} is a linear function of PEposPE_{pos} — i.e. there exists a matrix MkM_k (independent of pospos) such that PEpos+k=MkPEposPE_{pos+k} = M_k\,PE_{pos} for each frequency pair. Give the explicit 2×22\times2 rotation block. (8)

(c) Rotary Positional Embeddings (RoPE) apply a position-dependent rotation directly to QQ and KK. Show that the attention score between rotated query at position mm and rotated key at position nn depends only on the relative position mnm-n. Use a single 2-D frequency block with rotation matrix R(θ)R(\theta) and the property R(a)R(b)=R(ba)R(a)^\top R(b) = R(b-a). (6)


Question 3 — Building & Reasoning About a Decoder Block (18 marks)

(a) Write NumPy-style pseudocode for a causal (masked) self-attention forward pass on input XRn×dX \in \mathbb{R}^{n\times d} for a single head. Explicitly construct the causal mask and show where -\infty is inserted before the softmax. Explain why the mask must be applied before softmax rather than after. (8)

(b) Contrast Post-LN (x+Sublayer(LN(x))x + \text{Sublayer}(\text{LN}(x)) is not used; instead LN(x+Sublayer(x))\text{LN}(x + \text{Sublayer}(x))) with Pre-LN (x+Sublayer(LN(x))x + \text{Sublayer}(\text{LN}(x))). Explain, in terms of the residual stream and gradient flow, why Pre-LN trains more stably at large depth without learning-rate warmup. (6)

(c) In a standard encoder-decoder Transformer, identify the three distinct attention operations and, for each, state which sequence supplies the queries and which supplies the keys/values. (4)

Answer keyMark scheme & solutions

Question 1

(a) [6 marks] Each term qimkjmq_{im}k_{jm}: since q,kq,k independent, mean =E[q]E[k]=0= E[q]E[k] = 0. (1) Variance of one term: Var(qimkjm)=E[q2]E[k2]0=11=1\text{Var}(q_{im}k_{jm}) = E[q^2]E[k^2] - 0 = 1\cdot 1 = 1. (2) Sum of dkd_k independent terms: E[sij]=0E[s_{ij}] = 0, Var(sij)=dk\text{Var}(s_{ij}) = d_k. (2) To restore unit variance divide by standard deviation dk\sqrt{d_k}, not dkd_k (which would over-shrink to variance 1/dk1/d_k). Hence scaling 1/dk1/\sqrt{d_k} keeps logits O(1)O(1). (1)

(b) [8 marks] Softmax: ai=ezi/lezla_i = e^{z_i}/\sum_l e^{z_l}. For i=ji=j: aizi=ai(1ai)\frac{\partial a_i}{\partial z_i} = a_i(1-a_i). (2) For iji\ne j: aizj=aiaj\frac{\partial a_i}{\partial z_j} = -a_i a_j. (2) Compactly: aizj=ai(δijaj)\frac{\partial a_i}{\partial z_j} = a_i(\delta_{ij} - a_j). (1) Failure mode: without scaling, Var(s)=dk\text{Var}(s)=d_k grows, so softmax inputs become large in magnitude → softmax saturates → one ai1a_i \to 1, others 0\to 0. Then ai(1ai)0a_i(1-a_i)\to 0 and aiaj0a_ia_j\to 0, so the whole Jacobian 0\to 0: vanishing gradients, no learning signal through attention weights. (2) Scaling by 1/dk1/\sqrt{d_k} keeps logits O(1)O(1), keeping the softmax in its non-saturated regime and the Jacobian well away from zero. (1)

(c) [4 marks]

  • QKQK^\top: n×dkn\times d_k times dk×nd_k\times nO(n2dk)O(n^2 d_k) time, O(n2)O(n^2) memory. (1.5)
  • Softmax over n×nn\times n matrix: O(n2)O(n^2) time and memory. (1)
  • ×V\times V: n×nn\times n times n×dvn\times d_vO(n2dv)O(n^2 d_v) time, output O(ndv)O(n d_v). (1.5) Overall O(n2d)O(n^2 d) time, O(n2)O(n^2) memory (dominant).

(d) [4 marks] Idea: tiling / blockwise computation — never materialise the full n×nn\times n score matrix; process K,VK,V in blocks and accumulate the output using an online (streaming) softmax so only O(n)O(n) statistics (running max and running sum/output) are kept in fast SRAM. (2) Numerical technique: the online softmax with running max subtraction (log-sum-exp / max-rescaling), rescaling accumulated partial outputs as new blocks arrive to avoid overflow. (2)


Question 2

(a) [6 marks] Each of WQ,WK,WVW^Q,W^K,W^V maps dmodelhdk=dmodeld_{\text{model}} \to h\cdot d_k = d_{\text{model}}, so each is dmodel×dmodeld_{\text{model}}\times d_{\text{model}} = dmodel2d_{\text{model}}^2 params. (3) WOW^O maps concatenated hdv=dmodelh\cdot d_v = d_{\text{model}} back to dmodeld_{\text{model}}: dmodel2d_{\text{model}}^2. (1) Total =4dmodel2= 4 d_{\text{model}}^2, independent of hh because hdk=dmodelh\,d_k = d_{\text{model}} cancels the hh dependence. (2)

(b) [8 marks] Let ωi=100002i/d\omega_i = 10000^{-2i/d}. The pair is (sin(ωipos)cos(ωipos))\begin{pmatrix}\sin(\omega_i pos)\\ \cos(\omega_i pos)\end{pmatrix}. (2) Using angle addition: sin(ωi(pos+k))=sin(ωipos)cos(ωik)+cos(ωipos)sin(ωik)\sin(\omega_i(pos+k)) = \sin(\omega_i pos)\cos(\omega_i k) + \cos(\omega_i pos)\sin(\omega_i k) cos(ωi(pos+k))=cos(ωipos)cos(ωik)sin(ωipos)sin(ωik)\cos(\omega_i(pos+k)) = \cos(\omega_i pos)\cos(\omega_i k) - \sin(\omega_i pos)\sin(\omega_i k). (3) Hence Mk(i)=(cos(ωik)sin(ωik)sin(ωik)cos(ωik)),M_k^{(i)} = \begin{pmatrix} \cos(\omega_i k) & \sin(\omega_i k)\\ -\sin(\omega_i k) & \cos(\omega_i k)\end{pmatrix}, independent of pospos — a rotation by angle ωik\omega_i k. (3)

(c) [6 marks] Rotate query at position mm: q~m=R(mθ)q\tilde q_m = R(m\theta) q; key at nn: k~n=R(nθ)k\tilde k_n = R(n\theta) k. (2) Score =q~mk~n=qR(mθ)R(nθ)k= \tilde q_m^\top \tilde k_n = q^\top R(m\theta)^\top R(n\theta) k. (2) Using R(a)R(b)=R(ba)R(a)^\top R(b) = R(b-a): =qR((nm)θ)k= q^\top R((n-m)\theta) k, depending only on nmn-m. (2)


Question 3

(a) [8 marks]

def causal_self_attention(X, Wq, Wk, Wv, dk):
    Q = X @ Wq                       # (n, dk)
    K = X @ Wk
    V = X @ Wv
    scores = (Q @ K.T) / np.sqrt(dk) # (n, n)
    n = X.shape[0]
    mask = np.triu(np.ones((n, n)), k=1).astype(bool)  # True above diagonal
    scores[mask] = -np.inf           # future positions masked
    A = softmax(scores, axis=-1)      # row-wise
    return A @ V

Marks: Q/K/V projection (2), scaled scores (1), correct upper-triangular mask (2), -\infty before softmax (1). (6) Why before softmax: softmax normalises over all positions; inserting -\infty makes e=0e^{-\infty}=0 so masked positions get exactly zero weight and the remaining weights renormalise to sum to 1. Masking after softmax (zeroing) would leave the denominator including future tokens, so probabilities would not sum to 1 and would leak information about future magnitudes. (2)

(b) [6 marks] Post-LN: xl+1=LN(xl+F(xl))x_{l+1} = \text{LN}(x_l + F(x_l)) — the residual is inside the normalisation, so gradients must pass through every LN, and the identity path is not clean; at large depth this causes gradient magnitudes to explode/vanish, requiring warmup. (3) Pre-LN: xl+1=xl+F(LN(xl))x_{l+1} = x_l + F(\text{LN}(x_l)) — the residual stream is an unnormalised identity highway; gradients flow additively/unimpeded from top to bottom (each layer contributes an I+FI + \partial F term), keeping gradient norms bounded and stable, so training proceeds without warmup. (3)

(c) [4 marks]

  1. Encoder self-attention: Q, K, V all from the encoder input sequence. (1)
  2. Decoder masked self-attention: Q, K, V all from the (partial) decoder sequence, causally masked. (1.5)
  3. Encoder-decoder cross-attention: Q from the decoder, K and V from the encoder output. (1.5)

[
  {"claim":"Raw dot-product of two iid unit-variance d_k-dim vectors has variance d_k",
   "code":"dk=symbols('d_k',positive=True); var=dk*1; result = (var==dk)"},
  {"claim":"Softmax Jacobian diagonal is a_i(1-a_i)",
   "code":"z1,z2=symbols('z1 z2'); a1=exp(z1)/(exp(z1)+exp(z2)); J=diff(a1,z1); expr=a1*(1-a1); result = simplify(J-expr)==0"},
  {"claim":"MHA total projection params = 4*d_model^2 independent of h",
   "code":"dm,h=symbols('d_model h',positive=True); dk=dm/h; per=dm*(h*dk); total=3*per+ (h*dk)*dm; result = simplify(total-4*dm**2)==0"},
  {"claim":"Rotation composition R(a)^T R(b) = R(b-a) gives relative dependence",
   "code":"a,b=symbols('a b'); R=lambda t: Matrix([[cos(t),-sin(t)],[sin(t),cos(t)]]); lhs=simplify(R(a).T*R(b)); rhs=R(b-a); result = simplify(lhs-rhs)==zeros(2,2)"}
]