LLM Developer DocsLLM Docs

Loading the search index ...

↑ ↓ move, Enter open, Esc close

GitHub

Linear / ReLU / Initialisation

The linear layer and its backward pass, ReLU and its mask, Xavier uniform, and the starting value of every parameter, as ops.py and transformer.py write them.

ops.py

This page covers three building blocks from ops.py: the linear layer, ReLU and Xavier uniform. It also covers how init_params uses them. In the backward sections, dM always means the gradient of the scalar loss with respect to M.

Helper or inline?

The linear layer is written in two ways, so reading only ops.py gives the wrong picture.

Linear layer

What is it?

Linear layerImplemented

Basic operations

A linear layer multiplies its input by a weight matrix and adds a bias to every row. The function ops.linear_forward holds this formula, but the model never calls it. transformer.py writes the same expression in place at seven sites.

Why does the model need it?

It is how the model mixes the features of a token vector and moves between widths such as d, F and V. Query, key, value and the attention output are linear layers. So are both feed-forward layers and the head.

Mathematics

The linear layer, with b added to every row of XW #Link to equation: The linear layer, with b added to every row of XW
Y=XW+bY = XW + b

Notation & Shapes

shows how one entry of the product is computed.

What does each symbol mean?

SymbolMeaningIn codeShape
XXInput, one row per tokenX(T, a)
WWWeight matrix. A parameterW(a, c)
bbBias, added to every row by broadcasting. A parameterb(c,)
YYOutput, one row per token. The helper returns it without naming itnot named in the code(T, c)
a,ca, cInput and output width. They are d, F or V, depending on the layernot named in the codescalar

Tiny numerical example

Edit any cell of X, W, b or dY. The results include the backward pass, which the next section explains.

X @ W + b, and its backwardIllustrative ExampleHand-picked numbers that you can edit. These are not learned weights. Results come from the math engine, and tests compare it with the real Python on the starting values.
X
k0k1
t0
t1

W
j0j1j2
k0
k1

b
j0j1j2
b

dY
j0j1j2
t0
t1

(2, 2) @ (2, 3) → (2, 3): columns of X must equal rows of W. The result has the rows of X and the columns of W.

Row 0, column 0 of the product: 1 × 1 + 2 × -1 = -1

X @ W, shape (2, 3)
-122
-110
X @ W + b, shape (2, 3)
-131
-12-1
dW = X.T @ dY, shape (2, 3)
101
221
db = dY.sum(axis=0), shape (1, 3)
120
dX = dY @ W.T, shape (2, 2)
3-1
-22

Tensor shapes

Attention output projection, width stays the sametransformer.py:85
  1. O(T, d)(16, 32)
  2. out(T, d)(16, 32)

T = seq_len = 16. T is at most seq_len: generation uses the last seq_len ids, or fewer for a short prompt. d = emb_dim = 32.

First feed-forward layer, width growstransformer.py:113
  1. Xn(T, d)(16, 32)
  2. Z(T, F)(16, 128)

T = seq_len = 16. T is at most seq_len: generation uses the last seq_len ids, or fewer for a short prompt. d = emb_dim = 32. F = ff_dim = 128.

Second feed-forward layer, width shrinks backtransformer.py:116
  1. A(T, F)(16, 128)
  2. output(T, d)(16, 32)

T = seq_len = 16. T is at most seq_len: generation uses the last seq_len ids, or fewer for a short prompt. F = ff_dim = 128. d = emb_dim = 32.

Linear head, width becomes the vocabulary sizetransformer.py:166
  1. Xf(T, d)(16, 32)
  2. logits(T, V)(16, V)

T = seq_len = 16. T is at most seq_len: generation uses the last seq_len ids, or fewer for a short prompt. d = emb_dim = 32. V depends on the corpus. It is not a value in config.py.

Actual implementation

ops.pylinear_forwardGitHub: ops.py
def linear_forward(X, W, b):    return X @ W + b

Code and math connection

EquationRoleLinesCode
The linear layer, with b added to every row of XWComputes itops.py:8return X @ W + b
The linear layer, with b added to every row of XWWritten inlinetransformer.py:75Q = Xn @ params[p+"wq"] + params[p+"bq"] # (T,d), QUERY
The linear layer, with b added to every row of XWWritten inlinetransformer.py:76K = Xn @ params[p+"wk"] + params[p+"bk"] # KEY
The linear layer, with b added to every row of XWWritten inlinetransformer.py:77V = Xn @ params[p+"wv"] + params[p+"bv"] # VALUE
The linear layer, with b added to every row of XWWritten inlinetransformer.py:85out = O @ params[p + "wo"] + params[p + "bo"]
The linear layer, with b added to every row of XWWritten inlinetransformer.py:113Z = Xn @ params[p + "ff1_w"] + params[p + "ff1_b"]
The linear layer, with b added to every row of XWWritten inlinetransformer.py:116return A @ params[p + "ff2_w"] + params[p + "ff2_b"], dict(Xn=Xn, Z=Z, A=A)
The linear layer, with b added to every row of XWWritten inlinetransformer.py:166logits = Xf @ params["head_w"] + params["head_b"]
The linear layer, with b added to every row of XWTests ittests/test_ops.py:15assert np.allclose(linear_forward(X, W, b), [[-1, 3, 1], [-1, 2, -1]])

Where is it used?

The call graph shows no pipeline caller. The inline sites below are the real uses.

Source

ops.py, lines 7-8, linear_forward

PDF reference

Section 4.3, Eq. 19, p. 6

Used by

Not called by the pipeline.

Tested by: tests.test_ops.test_linear_worked_example

Written inline in transformer.py, line 75. Query.

Written inline in transformer.py, line 76. Key.

Written inline in transformer.py, line 77. Value.

Written inline in transformer.py, line 85. Attention output.

Written inline in transformer.py, line 113. First feed-forward layer.

Written inline in transformer.py, line 116. Second feed-forward layer.

Written inline in transformer.py, line 166. The head.

  • Tests it: tests/test_ops.py, line 15 (test_linear_worked_example), for The linear layer, with b added to every row of XW, occurrence 0
Status
Implemented

Implemented in

NoteInline equivalent: the pipeline does not call ops.linear_forward. It computes the same formula in place at transformer.py:75, transformer.py:76, transformer.py:77, transformer.py:85, transformer.py:113, transformer.py:116, transformer.py:166.
EquationRoleFileFunctionLinesGitHub
The linear layer, with b added to every row of XWComputes itops.pylinear_forward8GitHub: ops.py, line 8
The linear layer, with b added to every row of XWWritten inlinetransformer.pyattention_forward75GitHub: transformer.py, line 75
The linear layer, with b added to every row of XWWritten inlinetransformer.pyattention_forward76GitHub: transformer.py, line 76
The linear layer, with b added to every row of XWWritten inlinetransformer.pyattention_forward77GitHub: transformer.py, line 77
The linear layer, with b added to every row of XWWritten inlinetransformer.pyattention_forward85GitHub: transformer.py, line 85
The linear layer, with b added to every row of XWWritten inlinetransformer.pyffn_forward113GitHub: transformer.py, line 113
The linear layer, with b added to every row of XWWritten inlinetransformer.pyffn_forward116GitHub: transformer.py, line 116
The linear layer, with b added to every row of XWWritten inlinetransformer.pyforward166GitHub: transformer.py, line 166

Linear backward

What is it?

Linear layer, backwardImplemented

Basic operations

Given dY, the gradient of the loss on a linear layer's output, this computes three gradients. They are dW for the weights, db for the bias and dX for the input.

Why does the model need it?

dW and db are what the optimizer needs to update this layer's parameters. dX is what the layer before this one receives as its own dY, so the backward pass can continue.

Mathematics

dY holds the gradient of the loss on each output entry. The chain rule multiplies it by the derivative of that output entry, and adds over every entry that depends on the number in question.

The derivatives of one output entry Derived from implementation#Link to equation: The derivatives of one output entry
∂Ytj∂Wkj=Xtk,∂Ytj∂bj=1,∂Ytj∂Xtk=Wkj\frac{\partial Y_{tj}}{\partial W_{kj}} = X_{tk},\quad \frac{\partial Y_{tj}}{\partial b_{j}} = 1,\quad \frac{\partial Y_{tj}}{\partial X_{tk}} = W_{kj}

Summing over t gives dW and db. Summing over j gives dX.

Gradient on the weights #Link to equation: Gradient on the weights
dW=X⊤dYdW = X^{\top} dY
Gradient on the bias, the sum of dY over rows #Link to equation: Gradient on the bias, the sum of dY over rows
db=∑tdYtdb = \sum_{t} dY_{t}
Gradient on the input #Link to equation: Gradient on the input
dX=dY W⊤dX = dY\, W^{\top}

What does each symbol mean?

SymbolMeaningIn codeShape
dYdYGradient of the loss on the output Y. The caller supplies itdY(T, c)
XXThe input from the forward pass, kept in a cacheX(T, a)
WWThe weight matrixW(a, c)
dWdWGradient on the weightsdW(a, c)
dbdbGradient on the biasdb(c,)
dXdXGradient on the input, passed to the previous layerdX(T, a)

Tiny numerical example

A worked backwardIllustrative ExampleHand-picked numbers from test_linear_worked_example. They are not learned weights, and dY here is chosen by hand, not computed from a loss.
StepCodeValue
InputX[[1.0000, 2.0000], [0.0000, 1.0000]]
WeightsW[[1.0000, 0.0000, 2.0000], [-1.0000, 1.0000, 0.0000]]
Gradient on the outputdY[[1.0000, 0.0000, 1.0000], [0.0000, 2.0000, -1.0000]]
Gradient on the weightsdW = X.T @ dY[[1.0000, 0.0000, 1.0000], [2.0000, 2.0000, 1.0000]]
Gradient on the biasdb = dY.sum(axis=0)[1.0000, 2.0000, 0.0000]
Gradient on the inputdX = dY @ W.T[[3.0000, -1.0000], [-2.0000, 2.0000]]

One entry of each: dW[0][0] = 1 × 1 + 0 × 0 = 1, db[0] = 1 + 0 = 1, dX[0][0] = 1 × 1 + 0 × 0 + 1 × 2 = 3.

Checking this backward numericallyIllustrative ExampleA test-time idea, not part of the pipeline: gradcheck.numerical_grad is only called by tests. It nudges each number up and down by h = 1e-5 and measures how (Y * dY).sum(), whose gradient on Y is exactly dY, changes. Tests compare the central differences here with that real function.
GradientFrom the backward formulaFrom central differencesLargest gap
dW[1.0000, 0.0000, 1.0000][2.0000, 2.0000, 1.0000][1.0000, 0.0000, 1.0000][2.0000, 2.0000, 1.0000]3.8e-11
db[1.0000, 2.0000, 0.0000][1.0000, 2.0000, 0.0000]3.8e-11
dX[3.0000, -1.0000][-2.0000, 2.0000][3.0000, -1.0000][-2.0000, 2.0000]3.8e-11

Tensor shapes

dW has the shape of W, db the shape of b, and dX the shape of X. So the three real calls give:

Linear headtransformer.py:176
  1. dZ(T, V)(16, V)
  2. dW(d, V)(32, V)
  3. db(V,)
  4. dXf(T, d)(16, 32)

T = seq_len = 16. T is at most seq_len: generation uses the last seq_len ids, or fewer for a short prompt. V depends on the corpus. It is not a value in config.py. d = emb_dim = 32.

Second feed-forward layertransformer.py:119
  1. dOut(T, d)(16, 32)
  2. dW2(F, d)(128, 32)
  3. db2(d,)(32,)
  4. dA(T, F)(16, 128)

T = seq_len = 16. T is at most seq_len: generation uses the last seq_len ids, or fewer for a short prompt. d = emb_dim = 32. F = ff_dim = 128.

First feed-forward layertransformer.py:123
  1. dZ(T, F)(16, 128)
  2. dW1(d, F)(32, 128)
  3. db1(F,)(128,)
  4. dXn(T, d)(16, 32)

T = seq_len = 16. T is at most seq_len: generation uses the last seq_len ids, or fewer for a short prompt. F = ff_dim = 128. d = emb_dim = 32.

Actual implementation

ops.pylinear_backwardGitHub: ops.py
def linear_backward(X, W, dY):    dW = X.T @ dY    db = dY.sum(axis=0)    dX = dY @ W.T    return dX, dW, db

Code and math connection

EquationRoleLinesCode
The derivatives of one output entryComputes itops.py:11dW = X.T @ dY
The derivatives of one output entryComputes itops.py:12db = dY.sum(axis=0)
The derivatives of one output entryComputes itops.py:13dX = dY @ W.T
Gradient on the weightsComputes itops.py:11dW = X.T @ dY
Gradient on the weightsWritten inlinetransformer.py:95grads[p + "wo"] += c["O"].T @ dOut
Gradient on the weightsWritten inlinetransformer.py:106grads[p + "w" + n] += c["Xn"].T @ dM
Gradient on the weightsCalls ittransformer.py:119dA, dW2, db2 = linear_backward(c["A"], params[p + "ff2_w"], dOut)
Gradient on the weightsCalls ittransformer.py:123dXn, dW1, db1 = linear_backward(c["Xn"], params[p + "ff1_w"], dZ)
Gradient on the weightsCalls ittransformer.py:176dXf, dW, db = linear_backward(cache["Xf"], params["head_w"], dZ)
Gradient on the weightsTests ittests/test_ops.py:16dX, dW, db = linear_backward(X, W, dY)
Gradient on the bias, the sum of dY over rowsComputes itops.py:12db = dY.sum(axis=0)
Gradient on the bias, the sum of dY over rowsWritten inlinetransformer.py:96grads[p + "bo"] += dOut.sum(axis=0)
Gradient on the bias, the sum of dY over rowsWritten inlinetransformer.py:107grads[p + "b" + n] += dM.sum(axis=0)
Gradient on the inputComputes itops.py:13dX = dY @ W.T
Gradient on the inputWritten inlinetransformer.py:97dOh = split_heads(dOut @ params[p + "wo"].T, H)
Gradient on the inputWritten inlinetransformer.py:108dXn += dM @ params[p + "w" + n].T

Where is it used?

Source

ops.py, lines 10-14, linear_backward

PDF reference

Section 4.3, Eq. 20, 21, 22, p. 6

Used by

Called by: transformer.ffn_backward, transformer.loss_and_grads

Tested by: tests.test_ops.test_linear_worked_example

Written inline in transformer.py, line 95. dW for the attention output.

Written inline in transformer.py, line 96. db for the attention output.

Written inline in transformer.py, line 97. dX for the attention output, before it is split into heads.

Written inline in transformer.py, line 106. dW for query, key and value, in a loop.

Written inline in transformer.py, line 107. db for query, key and value.

Written inline in transformer.py, line 108. dX for query, key and value, added together.

Status
Implemented

Implemented in

NoteInline equivalent: the pipeline calls ops.linear_backward, and also computes the same formula in place, with no call, at transformer.py:95, transformer.py:106.
NoteInline equivalent: the pipeline calls ops.linear_backward, and also computes the same formula in place, with no call, at transformer.py:96, transformer.py:107.
NoteInline equivalent: the pipeline calls ops.linear_backward, and also computes the same formula in place, with no call, at transformer.py:97, transformer.py:108.
EquationRoleFileFunctionLinesGitHub
The derivatives of one output entryComputes itops.pylinear_backward11GitHub: ops.py, line 11
The derivatives of one output entryComputes itops.pylinear_backward12GitHub: ops.py, line 12
The derivatives of one output entryComputes itops.pylinear_backward13GitHub: ops.py, line 13
Gradient on the weightsComputes itops.pylinear_backward11GitHub: ops.py, line 11
Gradient on the weightsWritten inlinetransformer.pyattention_backward95GitHub: transformer.py, line 95
Gradient on the weightsWritten inlinetransformer.pyattention_backward106GitHub: transformer.py, line 106
Gradient on the bias, the sum of dY over rowsComputes itops.pylinear_backward12GitHub: ops.py, line 12
Gradient on the bias, the sum of dY over rowsWritten inlinetransformer.pyattention_backward96GitHub: transformer.py, line 96
Gradient on the bias, the sum of dY over rowsWritten inlinetransformer.pyattention_backward107GitHub: transformer.py, line 107
Gradient on the inputComputes itops.pylinear_backward13GitHub: ops.py, line 13
Gradient on the inputWritten inlinetransformer.pyattention_backward97GitHub: transformer.py, line 97
Gradient on the inputWritten inlinetransformer.pyattention_backward108GitHub: transformer.py, line 108

How this reaches a parameter update. The caller adds dW and db to grads, for example at transformer.py:178 and transformer.py:125. batch_loss_and_grads averages grads over the batch (train.py:61). clip_grad_norm may scale it (train.py:62), and opt.step uses it to change the parameter (train.py:66). dX is not stored. It goes to the previous layer as its dY. How the Model Learns follows the whole path.

ReLU

What is it?

ReLUImplemented

Basic operations

ReLU replaces every negative number with 0 and keeps the others. It works element by element, so the shape does not change.

Why does the model need it?

It is the only nonlinear step in the feed-forward layer. In general, two linear layers with nothing between them multiply out to a single linear layer.

Mathematics

ReLU, on each entry #Link to equation: ReLU, on each entry
Ati=max⁡(0, Zti)A_{ti} = \max(0,\, Z_{ti})

What does each symbol mean?

SymbolMeaningIn codeShape
ZZInput. relu_forward calls it X, and ffn_forward passes its ZX in relu_forward, Z in ffn_forward(T, F)
AAOutput. ffn_forward calls it A. It is not attention weights hereA in ffn_forward(T, F)

Tiny numerical example

ReLUIllustrative ExampleHand-picked numbers from test_relu. The third value is exactly 0, which is the case the strict > decides.
StepCodeValue
InputX[[-1.0000, 2.0000, 0.0000, 3.0000]]
Outputnp.maximum(0.0, X)[[0.0000, 2.0000, 0.0000, 3.0000]]

Tensor shapes

Inside the feed-forward layertransformer.py:114
  1. Z(T, F)(16, 128)
  2. A(T, F)(16, 128)

T = seq_len = 16. T is at most seq_len: generation uses the last seq_len ids, or fewer for a short prompt. F = ff_dim = 128.

Actual implementation

ops.pyrelu_forwardGitHub: ops.py
def relu_forward(X):    return np.maximum(0.0, X)

Code and math connection

EquationRoleLinesCode
ReLU, on each entryComputes itops.py:17return np.maximum(0.0, X)
ReLU, on each entryCalls ittransformer.py:114A = relu_forward(Z)
ReLU, on each entryTests ittests/test_ops.py:24assert np.allclose(relu_forward(Z), [0, 2, 0, 3])

Where is it used?

Source

ops.py, lines 16-17, relu_forward

PDF reference

Section 4.3, Eq. 29, p. 7

Used by

Called by: transformer.ffn_forward

Tested by: tests.test_ops.test_relu

Status
Implemented

Implemented in

EquationRoleFileFunctionLinesGitHub
ReLU, on each entryComputes itops.pyrelu_forward17GitHub: ops.py, line 17

ReLU backward

What is it?

ReLU, backwardImplemented

Basic operations

Given dA, the gradient on ReLU's output, this computes dZ, the gradient on its input. It keeps dA where the input was positive and sets every other entry to 0.

Why does the model need it?

The backward pass has to cross ReLU to reach the first feed-forward layer. Where the input is negative, a small change to it does not change the output, so no gradient passes back.

Mathematics

The derivative as the code uses it, with 0 at exactly Z = 0 Derived from implementation#Link to equation: The derivative as the code uses it, with 0 at exactly Z = 0
∂Ati∂Zti={1Zti>00Zti≤0\frac{\partial A_{ti}}{\partial Z_{ti}} = \begin{cases} 1 & Z_{ti} > 0 \\ 0 & Z_{ti} \le 0 \end{cases}
The mask-based backward #Link to equation: The mask-based backward
dZ=dA⊙1[Z>0]dZ = dA \odot \mathbf{1}[Z > 0]

What does each symbol mean?

SymbolMeaningIn codeShape
dAdAGradient of the loss on ReLU's outputdA(T, F)
ZZReLU's input from the forward pass, kept in the cacheZ(T, F)
1[Z>0]\mathbf{1}[Z > 0]The mask. NumPy multiplies its True and False as 1 and 0(Z > 0)(T, F)
dZdZGradient on ReLU's input. The function returns it without naming itdZ in ffn_backward(T, F)

Tiny numerical example

ReLU backwardIllustrative ExampleHand-picked numbers from test_relu. The third value is exactly 0, which is the case the strict > decides.
StepCodeValue
Input from the forward passZ[[-1.0000, 2.0000, 0.0000, 3.0000]]
Gradient on the outputdA[[1.0000, 1.0000, 1.0000, 1.0000]]
MaskZ > 0[[0.0000, 1.0000, 0.0000, 1.0000]]
Gradient on the inputdA * (Z > 0)[[0.0000, 1.0000, 0.0000, 1.0000]]

Tensor shapes

Inside the feed-forward backwardtransformer.py:122
  1. dA(T, F)(16, 128)
  2. dZ(T, F)(16, 128)

T = seq_len = 16. T is at most seq_len: generation uses the last seq_len ids, or fewer for a short prompt. F = ff_dim = 128.

Actual implementation

ops.pyrelu_backwardGitHub: ops.py
def relu_backward(Z, dA):    return dA * (Z > 0)

Code and math connection

EquationRoleLinesCode
The derivative as the code uses it, with 0 at exactly Z = 0Computes itops.py:20return dA * (Z > 0)
The mask-based backwardComputes itops.py:20return dA * (Z > 0)
The mask-based backwardCalls ittransformer.py:122dZ = relu_backward(c["Z"], dA)
The mask-based backwardTests ittests/test_ops.py:25assert np.allclose(relu_backward(Z, np.ones(4)), [0, 1, 0, 1])

Where is it used?

Source

ops.py, lines 19-20, relu_backward

PDF reference

Section 4.3, Eq. 29, p. 7

Used by

Called by: transformer.ffn_backward

Tested by: tests.test_ops.test_relu

Status
Implemented

Implemented in

EquationRoleFileFunctionLinesGitHub
The derivative as the code uses it, with 0 at exactly Z = 0Computes itops.pyrelu_backward20GitHub: ops.py, line 20
The mask-based backwardComputes itops.pyrelu_backward20GitHub: ops.py, line 20

How this reaches a parameter update. ReLU has no parameters, so nothing is stored here. dZ (transformer.py:122) goes into linear_backward for the first feed-forward layer (transformer.py:123), which produces the gradients of ff1_w and ff1_b. Those follow the path described under Linear backward.

Xavier uniform

What is it?

Xavier uniform initialisationImplemented

Initialization

Fills a new weight matrix with random numbers drawn evenly between -a and a, where a depends on the matrix's two sizes.

Why does the model need it?

In general, weights need random starting values, or every unit in a layer would compute the same thing, and a limit like this one aims to keep signals at a similar size as they pass through a layer. This repository does not measure that.

Mathematics

The limit #Link to equation: The limit
a=6fan_in+fan_outa = \sqrt{\frac{6}{\text{fan\_in} + \text{fan\_out}}}
Each entry is drawn evenly between -a and a #Link to equation: Each entry is drawn evenly between -a and a
Wij∼U[−a, a]W_{ij} \sim U[-a,\, a]
The variance this gives. The code sets a and never computes this #Link to equation: The variance this gives. The code sets a and never computes this
Var⁡(Wij)=a23=2fan_in+fan_out\operatorname{Var}(W_{ij}) = \frac{a^2}{3} = \frac{2}{\text{fan\_in} + \text{fan\_out}}

What does each symbol mean?

SymbolMeaningIn codeShape
fan_in\text{fan\_in}Rows of the matrixfan_inscalar
fan_out\text{fan\_out}Columns of the matrixfan_outscalar
aaThe limitascalar
WWThe new matrix. The function returns it without naming itnot named in the code(fan_in, fan_out)
UUThe uniform distribution, drawn by the generator passed inrng.uniform(fan_in, fan_out)

Tiny numerical example

tok_emb and head_w have no row here, because their size depends on V, and V depends on the corpus. Larger matrices get a smaller limit.

The limit for each matrixIllustrative ExampleThe sizes after the first row come from config.py. The limit is computed by the math engine. The lowest and highest values are stored results of running the real xavier_uniform with a generator seeded from config.py, and they are not the values of a trained or real model.
Matrixfan_infan_outLimit aLowest drawnHighest drawn
Tiny, hand-picked231.0954-0.88911.0420
w{q,k,v,o}32320.3062-0.30540.3056
ff1_w321280.1936-0.19340.1936
ff2_w128320.1936-0.19340.1936
pos_emb32320.3062-0.30540.3056
The tiny matrix itself, shape (2, 3)
0.6002-0.13390.7856
0.4324-0.88911.042

Tensor shapes

The output has shape (fan_in, fan_out), so these two arguments are the matrix's rows and columns.

A query, key, value or output matrixtransformer.py:32
  1. fan_in, fan_out(d, d)(32, 32)
  2. w{q,k,v,o}(d, d)(32, 32)

d = emb_dim = 32.

The first feed-forward matrixtransformer.py:36
  1. fan_in, fan_out(d, F)(32, 128)
  2. ff1_w(d, F)(32, 128)

d = emb_dim = 32. F = ff_dim = 128.

Actual implementation

ops.pyxavier_uniformGitHub: ops.py
def xavier_uniform(fan_in, fan_out, rng):    a = np.sqrt(6.0 / (fan_in + fan_out))    return rng.uniform(-a, a, size=(fan_in, fan_out))

Code and math connection

EquationRoleLinesCode
The limitComputes itops.py:4a = np.sqrt(6.0 / (fan_in + fan_out))
The limitTests ittests/test_ops.py:84assert np.abs(W).max() <= np.sqrt(6 / 64)
Each entry is drawn evenly between -a and aComputes itops.py:5return rng.uniform(-a, a, size=(fan_in, fan_out))
The variance this gives. The code sets a and never computes thisComputes itops.py:4a = np.sqrt(6.0 / (fan_in + fan_out))

Where is it used?

Only init_params calls it, and only train.main calls init_params, so it runs only at the start of training. talk.py loads its weights from a checkpoint instead.

Source

ops.py, lines 3-5, xavier_uniform

PDF reference

Section 4.3, Eq. 35, 36, p. 7

Used by

Called by: transformer.init_params

Tested by: tests.test_ops.test_xavier_bound

Status
Implemented

Implemented in

EquationRoleFileFunctionLinesGitHub
The limitComputes itops.pyxavier_uniform4GitHub: ops.py, line 4
Each entry is drawn evenly between -a and aComputes itops.pyxavier_uniform5GitHub: ops.py, line 5
The variance this gives. The code sets a and never computes thisComputes itops.pyxavier_uniform4GitHub: ops.py, line 4

Starting values

What is it?

Starting values of the parametersImplemented

Initialization

init_params creates every parameter. Each matrix starts from Xavier uniform. Each bias starts at zeros. Each LayerNorm starts with gamma at ones and beta at zeros.

Why does the model need it?

Training has to start from somewhere. Zero biases add nothing at first. With gamma at ones and beta at zeros, LayerNorm's output starts out equal to the normalised input.

Mathematics

Starting values, by kind of parameter #Link to equation: Starting values, by kind of parameter
W∼Xavier uniform,b=0,γ=1,β=0W \sim \text{Xavier uniform},\quad b = \mathbf{0},\quad \gamma = \mathbf{1},\quad \beta = \mathbf{0}

This table comes from running init_params. b{l} stands for each block.

What does each symbol mean?

SymbolMeaningIn codeShape
WWAny matrix, including both embedding tables and the headtok_emb, pos_emb, w{q,k,v,o}, ff1_w, ff2_w, head_wtwo axes
bbAny bias of a linear layerb{q,k,v,o}, ff1_b, ff2_b, head_bone axis
γ\gammaLayerNorm scaleln1_g, ln2_g, lnf_g(d,)
β\betaLayerNorm shiftln1_b, ln2_b, lnf_b(d,)

Tiny numerical example

What init_params returnedIllustrative ExampleStored result of running the real init_params with hand-picked sizes: V = 5, context_len = 3, emb_dim = 4, num_heads = 2, ff_dim = 6, num_layers = 1. These are not the model's values.
NameShapeLowestHighestXavier limit
tok_emb(5, 4)-0.71230.77670.8165
pos_emb(3, 4)-0.84470.87160.9258
b0.ln1_g(4,)1.00001.0000does not apply
b0.ln1_b(4,)0.00000.0000does not apply
b0.wq(4, 4)-0.64100.57620.8660
b0.bq(4,)0.00000.0000does not apply
b0.wk(4, 4)-0.85330.49700.8660
b0.bk(4,)0.00000.0000does not apply
b0.wv(4, 4)-0.81260.61210.8660
b0.bv(4,)0.00000.0000does not apply
b0.wo(4, 4)-0.82670.54390.8660
b0.bo(4,)0.00000.0000does not apply
b0.ln2_g(4,)1.00001.0000does not apply
b0.ln2_b(4,)0.00000.0000does not apply
b0.ff1_w(4, 6)-0.63880.72680.7746
b0.ff1_b(6,)0.00000.0000does not apply
b0.ff2_w(6, 4)-0.73930.71040.7746
b0.ff2_b(4,)0.00000.0000does not apply
lnf_g(4,)1.00001.0000does not apply
lnf_b(4,)0.00000.0000does not apply
head_w(4, 5)-0.78120.67930.8165
head_b(5,)0.00000.0000does not apply

Tensor shapes

Architecture

lists the shape of every parameter. For example, the head has one column per vocabulary id, and it is a separate matrix from tok_emb:

The head's weights and biastransformer.py:40
  1. head_w(d, V)(32, V)
  2. head_b(V,)

d = emb_dim = 32. V depends on the corpus. It is not a value in config.py.

Actual implementation

transformer.pyinit_paramsGitHub: transformer.py
def init_params(cfg: ModelConfig, rng):    d, F, V = cfg.emb_dim, cfg.ff_dim, cfg.vocab_size    p = {        "tok_emb": xavier_uniform(V, d, rng), # lookup table with one row per vocabulary token        "pos_emb": xavier_uniform(cfg.context_len, d, rng) # second table with one row per position, weighted average of values        # input to first block is tok_emb + pos_emb        # this makes cat at first position and fifth position different in a sentence    }    for l in range(cfg.num_layers):        b = f"b{l}." # key        p[b + "ln1_g"], p[b + "ln1_b"] = np.ones(d), np.zeros(d) # layer norm        for n in "qkvo":            p[b + "w" + n] = xavier_uniform(d, d, rng)            p[b + "b" + n] = np.zeros(d)        p[b + "ln2_g"], p[b + "ln2_b"] = np.ones(d), np.zeros(d)        p[b + "ff1_w"], p[b + "ff1_b"] = xavier_uniform(d, F, rng), np.zeros(F)        p[b + "ff2_w"], p[b + "ff2_b"] = xavier_uniform(F, d, rng), np.zeros(d)    p["lnf_g"], p["lnf_b"] = np.ones(d), np.zeros(d)    p["head_w"], p["head_b"] = xavier_uniform(d, V, rng), np.zeros(V)    return p

Code and math connection

EquationRoleLinesCode
Starting values, by kind of parameterComputes ittransformer.py:21"tok_emb": xavier_uniform(V, d, rng), # lookup table with one row per vocabulary token
Starting values, by kind of parameterComputes ittransformer.py:22"pos_emb": xavier_uniform(cfg.context_len, d, rng) # second table with one row per position, weighted average of values
Starting values, by kind of parameterComputes ittransformer.py:29p[b + "ln1_g"], p[b + "ln1_b"] = np.ones(d), np.zeros(d) # layer norm
Starting values, by kind of parameterComputes ittransformer.py:32p[b + "w" + n] = xavier_uniform(d, d, rng)
Starting values, by kind of parameterComputes ittransformer.py:33p[b + "b" + n] = np.zeros(d)
Starting values, by kind of parameterComputes ittransformer.py:35p[b + "ln2_g"], p[b + "ln2_b"] = np.ones(d), np.zeros(d)
Starting values, by kind of parameterComputes ittransformer.py:36p[b + "ff1_w"], p[b + "ff1_b"] = xavier_uniform(d, F, rng), np.zeros(F)
Starting values, by kind of parameterComputes ittransformer.py:37p[b + "ff2_w"], p[b + "ff2_b"] = xavier_uniform(F, d, rng), np.zeros(d)
Starting values, by kind of parameterComputes ittransformer.py:39p["lnf_g"], p["lnf_b"] = np.ones(d), np.zeros(d)
Starting values, by kind of parameterComputes ittransformer.py:40p["head_w"], p["head_b"] = xavier_uniform(d, V, rng), np.zeros(V)

Where is it used?

Source

transformer.py, lines 17-42, init_params

PDF reference

Section 4.5, p. 9

Status
Implemented

Implemented in

EquationRoleFileFunctionLinesGitHub
Starting values, by kind of parameterComputes ittransformer.pyinit_params21GitHub: transformer.py, line 21
Starting values, by kind of parameterComputes ittransformer.pyinit_params22GitHub: transformer.py, line 22
Starting values, by kind of parameterComputes ittransformer.pyinit_params29GitHub: transformer.py, line 29
Starting values, by kind of parameterComputes ittransformer.pyinit_params32GitHub: transformer.py, line 32
Starting values, by kind of parameterComputes ittransformer.pyinit_params33GitHub: transformer.py, line 33
Starting values, by kind of parameterComputes ittransformer.pyinit_params35GitHub: transformer.py, line 35
Starting values, by kind of parameterComputes ittransformer.pyinit_params36GitHub: transformer.py, line 36
Starting values, by kind of parameterComputes ittransformer.pyinit_params37GitHub: transformer.py, line 37
Starting values, by kind of parameterComputes ittransformer.pyinit_params39GitHub: transformer.py, line 39
Starting values, by kind of parameterComputes ittransformer.pyinit_params40GitHub: transformer.py, line 40