Roadmap / Case Study: OLMo 3

✓

OLMo 3's Sliding-Window Attention Pattern

easycase-studyolmo-3attentionmasking

The problem

Full attention lets every token look at all the tokens before it, and its cost grows with the square of the text length. OLMo 3 cuts the cost with sliding-window attention in three layers out of four: a token attends only to the most recent window tokens, itself included. Every fourth layer uses full causal attention, and the last layer is always full. In the released configurations the window is 4,096 tokens.

You will build the masks that implement this. A mask is a table of True (allowed) and False (forbidden), with one row per query position tt and one column per key position ss (section 7).

Which layers are full

Layer ℓ\ell, counting from 0, is "full" when ℓ mod 4=3\ell \bmod 4 = 3 (ℓ\ell divided by 4 leaves remainder 3), and the last layer is "full" whatever its index; all the others are "sliding".

Example. 8 layers: sliding, sliding, sliding, full, sliding, sliding, sliding, full. For 6 layers: sliding, sliding, sliding, full, sliding, full: layer 5 is the last, so it is full although 5 mod 4=15 \bmod 4 = 1.

The sliding-window mask

Query tt may attend to key ss when both conditions hold:

  • s≤ts \le t: no looking at the future (causal);
  • t−s<windowt - s < \text{window}: the key is among the last window tokens.

Example. T=5T = 5 and window = 2. Row tt allows the keys t−1t - 1 and tt (when they exist):

s=0s=0 s=1s=1 s=2s=2 s=3s=3 s=4s=4
t=0t=0 1 0 0 0 0
t=1t=1 1 1 0 0 0
t=2t=2 0 1 1 0 0
t=3t=3 0 0 1 1 0
t=4t=4 0 0 0 1 1

(1 is True.) A full layer would also allow the ones at the bottom-left.

With window >= T every key s≤ts \le t is within the window, so the mask is the ordinary causal mask: a lower triangle.

What to implement

  • layer_types(n_layers): the list of "sliding" and "full".
  • sliding_window_mask(T, window): the boolean (T,T)(T, T) mask.
  • attention_masks(T, n_layers, window): one mask per layer, following layer_types. A full layer gets the plain causal mask.

Requirements

  • sliding_window_mask(T, window) with window >= T is the ordinary causal mask.
  • No loops over positions.

Hint

Compare a column of query positions np.arange(T)[:, None] with a row of key positions np.arange(T)[None, :]: broadcasting builds the whole table at once, and the two conditions are two comparisons joined with &. In row tt the number of allowed positions is min(t + 1, window).

solution.py
Python idle

Press Run (⌘/Ctrl + Enter) to test your code.