CodeBlanks Logo Icon CodeBlanks

Encoder

The encoder processes input with attention and feed-forward layers.

The encoder

The encoder sees the whole input sequence and produces a context-aware representation for each position. Its block has two steps: self-attention (positions exchange information) and FFN (each position is transformed separately):

def ffn(x, W1, W2):
    return np.maximum(0, x @ W1) @ W2      # d -> 4d -> d

def encoder_blogu(x, Wq, Wk, Wv, W1, W2):
    d = x.shape[-1]
    A = softmax((x @ Wq) @ (x @ Wk).T / np.sqrt(d))
    x = layer_norm(x + A @ (x @ Wv))
    return layer_norm(x + ffn(x, W1, W2))

The FFN's hidden size is usually 4 times d_model; most of the parameters live here.

The FFN is per position

Applying the FFN row by row and applying it to the whole matrix at once give the same result, because the same weights are applied to each position separately. So changing one position changes only that row of the FFN output:

y1 = ffn(x, W1, W2)
x2 = x.copy(); x2[0] += 5
y2 = ffn(x2, W1, W2)
print(np.allclose(y1[1:], y2[1:]))     # True: the other rows are the same

In the attention layer, however, changing a single position affects all outputs.

Practice An encoder block

Complete the encoder_blogu function with the given helpers. Print the output's shape. Then print on one line with np.allclose that when only the first row of x changes, the other rows stay the same in the FFN output but change in the encoder block's output.

Starter code

model.py
import numpy as np

def softmax(z, eksen=-1):
    e = np.exp(z - z.max(axis=eksen, keepdims=True))
    return e / e.sum(axis=eksen, keepdims=True)

def layer_norm(x, eps=1e-5):
    m = x.mean(axis=-1, keepdims=True)
    v = x.var(axis=-1, keepdims=True)
    return (x - m) / np.sqrt(v + eps)

def ffn(x, W1, W2):
    return np.maximum(0, x @ W1) @ W2

rng = np.random.default_rng(0)
n, d = 4, 8
x = rng.normal(size=(n, d))
Wq, Wk, Wv = rng.normal(size=(3, d, d)) * 0.3
W1 = rng.normal(size=(d, 4 * d)) * 0.3
W2 = rng.normal(size=(4 * d, d)) * 0.3
x2 = x.copy()
x2[0] += 5

# buraya yaz

Sample solution

model.py
import numpy as np

def softmax(z, eksen=-1):
    e = np.exp(z - z.max(axis=eksen, keepdims=True))
    return e / e.sum(axis=eksen, keepdims=True)

def layer_norm(x, eps=1e-5):
    m = x.mean(axis=-1, keepdims=True)
    v = x.var(axis=-1, keepdims=True)
    return (x - m) / np.sqrt(v + eps)

def ffn(x, W1, W2):
    return np.maximum(0, x @ W1) @ W2

rng = np.random.default_rng(0)
n, d = 4, 8
x = rng.normal(size=(n, d))
Wq, Wk, Wv = rng.normal(size=(3, d, d)) * 0.3
W1 = rng.normal(size=(d, 4 * d)) * 0.3
W2 = rng.normal(size=(4 * d, d)) * 0.3
x2 = x.copy()
x2[0] += 5

def encoder_blogu(x):
    A = softmax((x @ Wq) @ (x @ Wk).T / np.sqrt(d))
    x = layer_norm(x + A @ (x @ Wv))
    return layer_norm(x + ffn(x, W1, W2))

print(encoder_blogu(x).shape)
print(np.allclose(ffn(x, W1, W2)[1:], ffn(x2, W1, W2)[1:]), np.allclose(encoder_blogu(x)[1:], encoder_blogu(x2)[1:]))

Expected output

(4, 8)
True False

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Does the FFN in the encoder carry information across positions?

  • No, it is applied to each position separately Correct answer
  • Yes, it mixes all positions
  • Only neighbouring positions
  • Only the first position

Explanation: Mixing across positions happens only in attention.

2 Apply ReLU in the FFN's hidden layer.

Question
return np.____(0, x @ W1) @ W2
Answer
return np.maximum(0, x @ W1) @ W2

Explanation: np.maximum compares element-wise; np.max returns a single value.

3 Match the effects.

Term Meaning
x[0] changes → FFN Only row 0 changes
x[0] changes → attention All rows change
W1 d → 4d
W2 4d → d

All lessons in Expert