Skip to content

← Tokenization & Embeddings step 11 of 15

Medium End-to-End

MLM Forward with Tied Output Head

Implement mlm_forward_tied: run a bidirectional transformer encoder and return vocabulary logits at selected positions. Use the same embedding table for both input lookup and the output projection.

This is weight tying: a hidden vector of width D is scored against every row of w_emb by multiplying it by w_emb.T. There is no separate vocabulary projection matrix. Compared with adding an independent (D, V) head, this shares V*D parameters between the two uses.

Inputs and output

  • input_ids: (N, T), integer-valued IDs in [0, V), delivered as floats.
  • mask_indicator: (N, T), finite values selecting rows where the value is greater than 0.5.
  • w_emb: (V, D), the shared token embedding table.
  • pos_embed: (T, D), position embeddings shared across the batch.
  • blocks_weights: (L, 6, D, D), containing L pre-normalization encoder blocks.
  • num_heads: a positive integer dividing D.

N, T, V and D are positive; L may be zero. Shapes are consistent. Model parameters are finite and share a floating-point dtype and device. IDs and selection indicators are on the same device. Leave all inputs unchanged.

Return raw logits of shape (M, V), where M is the count of indicator values strictly greater than 0.5. Preserve the parameter dtype and device. Visit examples in batch order and positions from left to right within each example. If no positions are selected, return shape (0, V).

Selection is separate from attention

Cast input_ids to integers and use them exactly as supplied. Any replacement with a mask token happens before this function. mask_indicator only selects which output rows to return; it does not change embeddings or block attention.

Every position can attend to all positions in its own sequence. Do not add a causal mask or treat token ID zero as padding. Keep different batch examples separate.

Embeddings and encoder blocks

Start with x = w_emb[input_ids] + pos_embed, broadcasting positions over the batch to obtain shape (N, T, D).

Apply blocks from 0 through L-1. For block i, the six matrices are [w_q, w_k, w_v, w_o, w_mlp1, w_mlp2], each of shape (D, D). The feed-forward width is D in this exercise. Each block performs:

h = LN(x)
q, k, v = h @ w_q, h @ w_k, h @ w_v
y = x + multi_head_attention(q, k, v) @ w_o
x = y + GELU(LN(y) @ w_mlp1) @ w_mlp2

LN normalizes each token over its D features using population variance: (token - mean) / sqrt(variance + 1e-5), with no learned scale or bias. For attention, split projected features into consecutive heads of width D/num_heads. Divide scores by sqrt(D/num_heads), apply stable softmax over key positions, and concatenate head outputs in their original order.

Use the tanh GELU approximation elementwise:

GELU(u) = 0.5 * u * (1 + tanh(sqrt(2/pi) * (u + 0.044715 * u**3)))

Build normalization, attention and GELU from basic tensor operations in either framework, without packaged attention or layer-normalization helpers. A stable softmax helper is allowed. There are no linear-layer biases, dropout or an additional final normalization.

Tied projection and row selection

After the last block, compute logits_all = x @ w_emb.T, then return the rows where mask_indicator > 0.5. Do not apply a vocabulary softmax or choose a predicted token. With L=0, project the token-plus-position embeddings directly.

For mask_indicator=[[0,1,1],[1,0,0]], return rows for (example, position) (0,1), (0,2), then (1,0). An indicator equal to 0.5 is not selected. The same w_emb supplies both the input vectors and every vocabulary output score.