PYQ · Mid Semester · Sep 23, 2024

Mid Semester 2024 — the paper, worked through.

This is the 2024 mid-semester paper for M24CS7.501: four questions, 30 marks, and 90 minutes. It moves from classical sequence models to transformer encoders and parameter-efficient fine-tuning. There is no official key for this paper, so the worked answers show a clear, well-structured way to respond.

Mid Sem 2024 4 questions 30 marks · 1.5 hrs RNN · attention · transformer · PEFT

§1The paper

The two five-mark questions cover RNNs and seq2seq attention. The two ten-mark questions cover the transformer encoder and efficient fine-tuning. Use the marks to decide how much detail each answer needs.

exam paper · page 1 / 1
Sep 23, 2024 mid-sem exam paper with 4 questions
the exam paper — all four questions on one page

Before you start

Same shape as 2023: every question is an open-ended "explain / describe / compare" prompt with no numerical work. But the balance has shifted. In 2023 the whole paper was pre-transformer; here, two thirds of the marks (20 out of 30) are on transformer-era material: the encoder block itself, and PEFT methods that only make sense once you have a large pretrained transformer to fine-tune. The 2024 syllabus has caught up with the field.

  1. The two 5-mark questions want tight answers, so think five distinct correct claims per question, one for each mark. Overwriting them steals time from Q3 and Q4.
  2. The two 10-mark questions want structured answers: a diagram, the defining equations, a walk through each sub-component, and a "why does it look this way" paragraph. A wall of prose without equations will not clear a 10-marker.
⚠ Watch out

Q4 asks for architectures, plural. Answering only "LoRA is low-rank, Adapters are bottlenecks" without sketching where each of them lives in the transformer block loses half the mark. The word "architectures" in the question is not decoration.


§2Q1 · Long-distance dependencies in RNNs

Q1 5 marks RNN LSTM GRU BPTT

The question, verbatim

1. Explain how we can enable handling long distance dependencies in RNNs. [5 marks]

How to approach it

Only five marks, so keep it tight. Start with the failure mode a vanilla RNN has — the vanishing / exploding gradient in back-propagation through time — because "enable handling" only makes sense once you have named what stops it. Then give the fix: gating, in the LSTM's cell-state form and the GRU's update-gate form, both of which turn the multiplicative gradient path into a nearly additive one. Close with the more radical answer — attention removes recurrence altogether. Five marks, five distinct claims.

Worked answer

Where the problem comes from

A vanilla RNN reads a sequence one token at a time and updates a hidden state through a shared recurrence:

h t = tanh ( W h h h t − 1 + W x h x t + b )

In principle, this recurrence can carry information from step 1 to step 100 — the hidden state at any late position is a function of every earlier one. But training with back-propagation through time unrolls the network into a T -layer deep computation, and the gradient of the loss with respect to an early hidden state is a product of Jacobians, one per time step. That product either vanishes (spectral norm < 1, geometric decay to zero) or explodes (spectral norm > 1, geometric blow-up). Long-range gradients never reach the early weights — so a vanilla RNN has the capacity for long dependencies but not the trainability.

Fix 1 — gating (LSTM, GRU)

Both LSTM and GRU replace the single tanh update with a small circuit of sigmoid gates that decide, per component, how much of the past to keep and how much of the new candidate to write. On any dimension where the "keep" gate is near 1, the state passes through unchanged and the gradient across that step is close to the identity — the multiplicative gradient path becomes an additive one.

LSTM. Keeps a separate cell state c t that is updated by an additive rule, gated by a forget gate f t and an input gate i t :

c t = f t ⊙ c t − 1 + i t ⊙ c t ~

This is the famous constant error carousel: when the forget gate is near 1, gradients flow along the cell-state highway across many time steps essentially unchanged. Long dependencies stop being killed by BPTT.

GRU. Fuses the cell and hidden state into one vector and uses only two gates — an update gate z t and a reset gate r t . The update gate interpolates between the old state and the new candidate:

h t = ( 1 − z t ) ⊙ h t − 1 + z t ⊙ h t ~

When z t is near 0, the past is preserved verbatim and the gradient across that step is again close to the identity. Fewer parameters than LSTM, same additive-highway trick.

Under the hood · one line

Gates turn the recurrence's multiplicative gradient path into a nearly additive one on any step the gate says "keep". That is the single reason gated RNNs learn long dependencies where vanilla RNNs cannot.

Fix 2 — get rid of recurrence entirely (attention / self-attention)

The other answer is more radical: if recurrence is the reason long-range signal is fragile, remove it. Self-attention lets any position look directly at any other position with a single matrix multiply — the distance-1 shortcut a plain RNN never had. Every token has an O ( 1 ) path length to every other token, so long-range dependencies stop being a training problem at all. This is the escape hatch the transformer takes, and it is what makes the rest of this paper (Q3, Q4) possible.

✦ Key takeaway

Notice the two answers point in opposite directions. Gates keep the recurrence and patch its gradient path. Attention throws out the recurrence and gives every position a direct edge to every other. Both work — but only the second scales, which is why the field moved on.


§3Q2 · Why attention in seq2seq

The question, verbatim

2. Why is attention needed in a sequence-to-sequence model? Explain with attention function description. [5 marks]

How to approach it

The question is two halves. First, why: name the bottleneck in vanilla seq2seq and argue that different target words need different source content, so the decoder needs a fresh source view at each step. Second, the function: write the three-line attention block — score, softmax, weighted sum — and name at least one concrete score function (additive Bahdanau or multiplicative Luong). Five marks, so keep it to one crisp diagram-in-words plus the equations.

Worked answer

Why attention is needed

A vanilla encoder-decoder for machine translation (Sutskever et al. 2014) reads the whole source sentence with an RNN and summarises it into a single fixed-length vector — the final encoder hidden state — which is then handed to the decoder as its only source-side input. The decoder must generate the entire target sentence from that one vector.

Two problems fall out of this design:

  1. The bottleneck. A fixed-size vector cannot carry all of a long source sentence. Empirically, translation quality drops sharply once the source exceeds roughly 20–30 tokens, because the encoder is being asked to compress unbounded content into a bounded state.
  2. Different targets need different source content. Consider translating "the red car is fast" into French — "la voiture rouge est rapide". When the decoder is producing voiture, it needs to look at car. When it is producing rouge, it needs to look at red. One fixed vector cannot serve both — it averages them or forgets one. What the decoder actually needs is a different source view at every target step.

Attention (Bahdanau et al. 2015) fixes both. The encoder keeps all its per-position hidden states h 1 , … , h n . At each decoder step, a soft alignment is computed over those states and a fresh context vector c t is produced — a weighted sum of encoder states, weighted by how well each source position matches the current decoder state. The decoder can "look back" at any source token at any time.

The attention function

At decoder step t , attention runs three lines:

e t , i = score ( s t − 1 , h i )  alignment score  α t , i = exp ( e t , i ) ∑ j = 1 n exp ( e t , j )  attention weight  c t = ∑ i = 1 n α t , i h i  context vector 

The score function is the model's judgement of how relevant source position i is to producing the next target token. Two standard choices:

Additive (Bahdanau, 2015) — a tiny one-hidden-layer feedforward network on the concatenated decoder and encoder states:

score ( s t − 1 , h i ) = v T tanh ( W s s t − 1 + W h h i )

Multiplicative / dot (Luong, 2015) — a bilinear form, cheaper because it is essentially a matrix multiply:

score ( s t , h i ) = s t T W h i

Once c t is computed, the decoder produces the next word from a combination of its own state and the context vector: p ( y t | y < t , x ) = softmax ( W [ s t ; c t ] ) .

✦ Key takeaway

Attention is not just an accuracy fix for long sentences. It is the realisation that "which piece of input do I need right now?" is itself something the model can learn. Once you can learn a soft lookup over encoder states, you no longer need a recurrence to carry information across positions — you can just attend. That observation is what leads to the transformer.


§4Q3 · Transformer encoder block, in detail

The question, verbatim

3. Describe the transformer encoder block in detail. [10 marks]

How to approach it

A ten-mark structural question. Walk from input to output, naming each sub-component and giving its defining equation. The full checklist: input embedding + positional encoding, multi-head self-attention (with the scaled dot-product formula and how heads are computed and combined), residual connection and LayerNorm, position-wise feedforward network, residual and LayerNorm again. Close with the complexity O ( n 2 · d ) and one line on why the block looks this way. Aim for one equation-plus-paragraph per sub-component — that is roughly two marks each, which lines up with the ten.

Worked answer

Setup — the block's input and output

An encoder block takes a matrix x ∈ ℝ n × d model — one row per input token, embedded into a shared d model -dimensional space. It returns a tensor of the same shape, so blocks stack cleanly. A transformer encoder is N such blocks in series (the original paper uses N = 6 ).

Before the first block, two things happen once to the input:

  1. Each token id is turned into a d model -dimensional token embedding via a learned lookup table.
  2. A positional encoding — a fixed sinusoidal signal or a learned vector — is added to each embedding. Self-attention is otherwise position-agnostic; without this, the model would treat the sentence as a bag of tokens.

Sub-layer 1 — multi-head self-attention

The first sub-layer lets each token look at every other token in the sequence. For a single head, three learned projections turn the input into queries, keys, and values:

Q = x W Q , K = x W K , V = x W V

where W Q , W K ∈ ℝ d model × d k and W V ∈ ℝ d model × d v . Scaled dot-product attention is then:

Attention ( Q , K , V ) = softmax ( Q K T d k ) V

Reading it row by row: for each query token, dot it with every key to get a raw score, divide by d k so the softmax does not saturate at high dimension, softmax over the sequence to get attention weights, and take a weighted sum of the value vectors. Each output row is a soft lookup — a mixture of value vectors, weighted by query-key similarity.

Multi-head. Instead of one large attention with dimension d model , the block runs h heads in parallel with per-head dimension d k = d model / h . Each head has its own W Q , W K , W V . Their outputs are concatenated and projected back:

MultiHead ( x ) = Concat ( head 1 , … , head h ) W O

Different heads learn to attend to different kinds of structure — one may track syntactic dependencies, another may track co-reference. Splitting into heads is essentially free (the total parameter count is roughly the same as a single-head attention of dimension d model ) but is strictly more expressive.

Under the hood · why the √dk

Dot products of two random d k -dim vectors have variance proportional to d k . Without the d k denominator, softmax inputs grow with d k , saturating the softmax and killing gradients. Dividing by d k keeps the scale roughly unit variance regardless of dimension.

Residual connection and LayerNorm (round 1)

The attention output is added back to its input via a residual connection, and the sum is normalised:

z 1 = LayerNorm ( x + MultiHead ( x ) )

The residual gives gradients a direct path around the sub-layer, letting deep stacks train stably. LayerNorm normalises each token vector to zero mean and unit variance across its features, then rescales with learned per-feature γ and β . Unlike BatchNorm, it depends only on the token itself, so it works with variable sequence lengths and small batches.

Sub-layer 2 — position-wise feedforward network

The second sub-layer is a small MLP applied independently to each position. Two linear layers with a non-linearity in between:

FFN ( x ) = max ( 0 , x W 1 + b 1 ) W 2 + b 2

with W 1 ∈ ℝ d model × d ff and W 2 ∈ ℝ d ff × d model — the inner dimension is typically 4 d model . The original paper uses ReLU; modern implementations (BERT, GPT-2 onwards) use GELU. The point is that self-attention only mixes information across positions linearly; the FFN adds a per-token non-linear transformation, and does most of the block's per-position compute.

Residual and LayerNorm (round 2)

Same pattern as after attention:

z 2 = LayerNorm ( z 1 + FFN ( z 1 ) )

z 2 is the output of one encoder block, ready to be fed into the next.

The block, at a glance

Step What it does Shape
Input token embedding + positional encoding n × d model
Multi-head self-attention each token attends to every token; h heads run in parallel n × d model
Add & Norm residual, then LayerNorm across features n × d model
Position-wise FFN two-layer MLP, applied independently at each position n × d model
Add & Norm residual, then LayerNorm again n × d model

Complexity

Self-attention forms an n × n attention matrix, so the block is O ( n 2 · d model ) in time and O ( n 2 ) in memory. Quadratic in the sequence length. This is the cost the field has spent the last five years trying to reduce — Flash Attention, sparse attention, linear attention, state-space models. But inside one encoder block, this cost is what buys you an O ( 1 ) path length between any two tokens, which is the whole point.

✦ Key takeaway

Read the block as two sub-layers with the same shape — each is (mix, then residual + norm). Self-attention mixes information across tokens; the FFN mixes information across feature dimensions. Every subsequent architecture in this course either changes how the across-tokens mixer works (Mamba, Flash Attention, sparse variants) or how the across-features mixer scales (Mixture-of-Experts). The template is the same.


§5Q4 · LoRA vs Adapters

The question, verbatim

4. How does LoRA Finetuning approach differ from Adapters? Explain with architectures. [10 marks]

How to approach it

Frame both as parameter-efficient fine-tuning (PEFT) — same problem (fine-tune a large pretrained model cheaply by touching only a small number of new parameters, keeping the original weights frozen), two different architectural answers. Sketch each one: where it plugs into the transformer block, what its forward equation looks like, how many parameters it adds. Then give the differences on axes that actually matter: serial vs parallel placement, inference latency, mergeability, expressiveness. The word "architectures" in the question is a rubric signal — you need diagrams-in-words or pictures, not just prose.

Worked answer

The shared problem

Full fine-tuning of a modern pretrained transformer means updating all of its billions of parameters and storing a full-sized checkpoint per downstream task. That is expensive to train and impossible to serve if you have many tasks. Parameter-efficient fine-tuning methods freeze the original weights W and add a small number of new, trainable parameters — typically 0.1%–5% of the base model — that specialise it to the task. Two of the most widely used are Adapters (Houlsby et al. 2019) and LoRA (Hu et al. 2021).

Adapters — architecture

An adapter is a small bottleneck MLP inserted inside the transformer block, in series with the frozen sub-layers. Given an input h ∈ ℝ d , the adapter does:

Adapter ( h ) = h + W up σ ( W down h )

Three pieces: a down-projection W down ∈ ℝ r × d that squashes to a small bottleneck of size r ≪ d , a non-linearity σ (GELU / ReLU), and an up-projection W up ∈ ℝ d × r back to the model dimension. A residual connection carries the input around it, and W up is initialised to zero so the adapter starts as an identity — the model behaves exactly like the frozen base at initialisation and only drifts as the adapter learns.

The adapter is inserted twice per transformer block: once after the multi-head self-attention sub-layer, once after the FFN sub-layer. The block becomes:

x
 ├─> MultiHead-SelfAttn (frozen)
 ├─> Add + LayerNorm
 ├─> Adapter  (trainable, ~few % params)
 ├─> FFN (frozen)
 ├─> Add + LayerNorm
 ├─> Adapter  (trainable)
 └─> out

Only W down , W up , and the LayerNorm parameters are trained. Everything else is frozen. Adapters typically add ~1–5% of the base model's parameter count per task.

LoRA — architecture

LoRA does not insert anything into the block. It adds a trainable low-rank update in parallel to selected frozen weight matrices — typically the query and value projections inside self-attention. If W ∈ ℝ d × d is one of the frozen projections, LoRA replaces it with:

W ′ = W + Δ W = W + α r B A

with A ∈ ℝ r × d and B ∈ ℝ d × r , r ≪ d . The factor α / r is a scaling constant. A is initialised at random and B is initialised to zero, so at the start of training B A = 0 and the model is exactly the frozen base — the same "start as identity" trick as adapters.

Forward pass through a LoRA-augmented projection:

h = W x + α r B ( A x )

Two matrix multiplies down the low-rank path, in parallel with the frozen W x . Trained parameters per LoRA-adapted matrix: 2 d r . With rank r = 8 or 16 applied to just W Q and W V , LoRA typically trains <1% of the base model's parameters.

x ──> W (frozen)     ──┐
      │                 ├─> add ─> h
      └─> A ─> B (train)┘

The critical differences

Axis Adapters LoRA
Where it sits Inside the block, after each sub-layer Parallel to chosen weight matrices (usually W Q , W V )
Placement in the graph Serial — the forward pass goes through it Parallel — added to the frozen matmul
Inference latency Adds latency at every layer, both training and inference Zero extra latency at inference — the update B A can be merged into W : W ′ = W + B A
Trained shape Bottleneck MLP with a non-linearity Linear low-rank factorisation, no non-linearity
Trainable params ~1–5% of the base Often <1% of the base (tunable via r )
Task switching Swap adapter weights in place Swap by unmerging one B A and merging another (or serve unmerged)
Under the hood · why LoRA has zero inference cost

Because B A and W have the same shape, they can be summed once, after training, into a single W ′ . Inference then runs exactly the same matmul the original model did — one matrix multiply, no extra layers. Adapters cannot do this because the non-linearity σ between the down- and up-projections is not a linear operation, so there is nothing to fold back into the frozen weights.

Why each design exists

Adapters came first and are the more general recipe — the bottleneck can learn any non-linear transformation, and the same trick generalises to inserting small modules anywhere in the graph. Their weakness is that they add serial depth: every adapter is one more layer of forward computation the request has to pass through, and stacking them across all 24 or 32 transformer blocks measurably slows inference.

LoRA takes a specific bet: the update Δ W that fine-tuning would learn is approximately low-rank, so it can be represented as B A with very small r . In exchange for that assumption, LoRA gets three practical wins — fewer trained parameters, no inference latency (thanks to mergeability), and effortless multi-task serving (keep W , keep a stack of small ( A , B ) pairs on disk, load the pair for the task you want).

✦ Key takeaway

The single sentence that captures the difference: adapters make the network deeper, LoRA makes selected weight matrices richer. Because "deeper" costs inference latency and "richer via a low-rank update" does not, LoRA has become the default parameter-efficient method for large models — every serving stack from Hugging Face PEFT to vLLM has first-class LoRA support, and multi-tenant LoRA serving is a real production pattern.