Concat + project
# grade this stage (from your course directory)
npx theinference test --stage concat-project --watchRun all 12 heads, concatenate their outputs back to [T, 768], then mix them with the output projection. That final step is what makes them a team — and it completes GPT-2's attention sublayer.
head0[T,64] head1[T,64] ... head11[T,64]
|__________ concat along columns __________|
|
v
concat [T,768] (head h -> cols h*64 : (h+1)*64)
|
@ c_proj[768,768] + b
v
out [T,768]
What to build
Implement runMHA
Implement runMHA(x, layer): run each of the 12 heads (reuse runHead), concatenate to [T, 768] (head h → columns h·64 : (h+1)·64), then apply c_proj: concat @ c_proj.weight + c_proj.bias. Load h.{layer}.attn.c_proj.{weight,bias}.
export function runMHA(x: number[][], layer: number): number[][] {
// x: [T, 768], layer: which block's weights -> [T, 768]
}
The 12 heads run independently — until c_proj. That final [768, 768] matmul is what lets head 3's finding flow into head 7's channels; without it the heads never talk.
Returning the bare concatenation is a real bug, not a shortcut: the concat is not the attention sublayer's output. The sublayer output is concat @ c_proj.weight + c_proj.bias. (Same Conv1D [in, out] layout — x @ W, no transpose.)
Run on block 0; your [T, 768] output is compared with allclose, atol 1e-4.