Build your own GPT-2
Load the GPT-2 weights·8 min

Read a tensor

0 / 2
# grade this stage (from your course directory)
npx theinference test --stage read-a-tensor --watch

Row id of wte is everything GPT-2 knows about token id before it has seen any context — 768 numbers that are that token's entire identity. For id 0 those numbers begin [-0.1101, -0.0393, 0.0331, …], the exact floats from the byte-map two steps back. You parsed the header; now read those numbers straight out of the file, by name. This is the loader interface every later chapter leans on.

Why "first row" = "first 768 floats in memory"
wte.weight  shape [50257, 768]  (row-major / C order)
memory:  [ row0[0..767] | row1[0..767] | row2[0..767] | … ]
          └── first 768 floats ──┘
runTensorSlice("wte.weight") → row 0 → [-0.1101, -0.0393, 0.0331, …]  (768 of them)

What to build

Your task

Implement runTensorSlice

Implement runTensorSlice in tests/adapters.ts. Given a tensor name, return its first row as a number[]. Load the file with the loader you just wrote; read the path from process.env.THEINFERENCE_WEIGHTS ?? "./weights/model.safetensors" (the convention every weighted step uses).

tests/adapters.ts
export function runTensorSlice(name: string): number[] {
  // -> the first row of the named tensor (e.g. wte.weight -> 768 values)
}
How it's graded

Run on wte.weight; your length-768 row is compared to the reference with allclose, atol 1e-4.

This loader is the foundation

Every weighted step from here on reads its weights through this same name → tensor map: runTokenEmbed pulls wte.weight, runQKV pulls h.{i}.attn.c_attn.{weight,bias}, and so on. Factor it once so runTensorSlice, runTokenEmbed, runQKV, … all share a single loader instead of re-parsing the file each call.

Read the path from process.env.THEINFERENCE_WEIGHTS ?? "./weights/model.safetensors" so the grader can point your code at its own copy of the weights.

Knowledge check

For wte.weight (shape [50257, 768], row-major), what are its first five F32 values?