Mid-level interview questionAsked at Amazon, Datadog · interview write-up
Implement causal multi-head attention, add a KV cache for token-by-token decoding, then extend it to grouped-query attention.
Model answer
Write it once as GQA: plain MHA is the case where key-value heads equal query heads, and multi-query is one key-value head.
import torch, torch.nn as nn, torch.nn.functional as F
class GQA(nn.Module):
def __init__(self, d, n_q, n_kv):
super().__init__()
self.hd, self.nq, self.nkv = d // n_q, n_q, n_kv
self.q = nn.Linear(d, d, bias=False)
self.kv = nn.Linear(d, 2 * n_kv * self.hd,
bias=False)
self.o = nn.Linear(d, d, bias=False)
def forward(self, x, cache=None):
B, T, _ = x.shape
q = self.q(x).view(B, T, self.nq, self.hd)
k, v = self.kv(x).view(
B, T, 2, self.nkv, self.hd).unbind(2)
q, k, v = (t.transpose(1, 2) for t in (q, k, v))
if cache is not None:
k = torch.cat([cache[0], k], dim=2)
v = torch.cat([cache[1], v], dim=2)
cache = (k, v) # n_kv heads: the memory saving
r = self.nq // self.nkv
k = k.repeat_interleave(r, dim=1)
v = v.repeat_interleave(r, dim=1)
S = k.shape[2] # cached + new positions
# new query i sits at position S - T + i
m = torch.ones(T, S, dtype=torch.bool,
device=x.device).tril(S - T)
a = q @ k.transpose(-2, -1) / self.hd ** 0.5
a = a.masked_fill(~m, float("-inf"))
y = F.softmax(a, dim=-1) @ v
y = y.transpose(1, 2).reshape(B, T, -1)
return self.o(y), cache
Interviewers check three things: the mask is offset by the cached length, the cache stores keys and values before repeating them, so it shrinks by the group factor, and cached decoding reproduces a full forward pass token for token.
Practise the rest
11 Context & KV cache questions, 492 in all, each with a model answer. Free.
More from Amazon interview questions, Datadog interview questions.
More Context & KV cache interview questions
- What is a context window, and why can't GPT-2 small take a 1,025th token even with plenty of memory?
- Users say your summariser 'takes ages to start' on long documents but then writes quickly. Explain what's happening in terms of prefill and decode.
- What does the KV cache store, why is using it exact rather than an approximation, and how much does it save?
- You're sizing GPUs for a chat service. How do you estimate KV-cache memory, and which levers would you pull if it doesn't fit?
- Request A has a 960-token prompt and 10 output tokens. Request B has a 16-token prompt and 200 output tokens. Same model and machine: which finishes first, and what does that tell you about a chat app that resends the full history every turn?
- What are multi-head and grouped-query attention, and what does GQA do to the KV cache?