import numpy as np, matplotlib.pyplot as plt
rng = np.random.default_rng(0)
tokens = ["the", "cat", "sat", "on", "mat"]
N, d = len(tokens), 16
X = rng.normal(size=(N, d))
Wq, Wk, Wv = (rng.normal(size=(d, d)) * 0.5 for _ in range(3))
Q, K, V = X @ Wq, X @ Wk, X @ Wv
def softmax(z):
z = z - z.max(-1, keepdims=True); e = np.exp(z); return e / e.sum(-1, keepdims=True)
mask = np.triu(np.ones((N, N)), 1) * -1e9 # causal: no peeking ahead
A = softmax(Q @ K.T / np.sqrt(d)) # full self-attention
A_caus = softmax(Q @ K.T / np.sqrt(d) + mask) # decoder (masked)
fig, ax = plt.subplots(1, 2, figsize=(6.2, 2.6))
for a, M, t in zip(ax, [A, A_caus], ["full self-attention", "causal (decoder) mask"]):
a.imshow(M, cmap="viridis", vmin=0, vmax=1)
a.set_xticks(range(N)); a.set_xticklabels(tokens, fontsize=7, rotation=45)
a.set_yticks(range(N)); a.set_yticklabels(tokens, fontsize=7); a.set_title(t, fontsize=8)
plt.tight_layout(); plt.show()