The problem
While generating text, a model produces one token at a time, and each new token attends to all the previous ones. The keys and values of the past tokens never change (the model is causal: they depend only on tokens before them), so recomputing them at every step is wasted work. The KV (key-value) cache stores them: each step computes the query, key and value of the new token only, appends its key and value to the cache, and attends over the whole cache.
You will write one step of attention with a cache, for one head, and the formula for the size of the cache.
One step with a cache
Use one attention head with weights p = {"Wq", "Wk", "Wv"}, each of shape . For the new token with embedding x_t of shape :
- compute , , , each of numbers;
- append and as a new row of the cached keys and values: the cache now holds rows;
- scores: the dot product of with every cached key, divided by ;
- softmax of the scores, and the output is the weighted average of the cached values.
No causal mask is needed: everything in the cache is in the new token's past (or is the token itself).
Example. , (identity), (doubles the second number). Three tokens arrive: , , .
- Step 1: , cache holds one key and one value . The only weight is 1, so the output is .
- Step 2: ; keys ; values . Scores , weights , output .
- Step 3: ; keys ; values . Scores , weights , output .
The size of the cache
For every layer, every key/value head and every cached token, the cache stores one key and one value of numbers each:
where is the number of bytes per value, is the number of layers, the number of key/value heads, the head size, the number of cached tokens, and the 2 is for keys and values.
Example. , , , , 2 bytes per value (the BF16 (brain floating point) format): bytes, 4 GiB (gibibytes) for one sequence. With it is 1 GiB.
What to implement
attend_step(x_t, cache, p):cacheis a dict with"k"and"v", arrays of shape for the tokens seen so far (it starts with shape ). Return(out, new_cache), withoutof shape . Do not modify the input cache.kv_cache_bytes(n_layers, n_kv_heads, head_dim, n_tokens, bytes_per_value): the formula above.
Requirements
- Running
attend_stepon the tokens one by one must give exactly the rows of full causal attention over the whole sequence (the tests compare with a reference). - Scale scores by and use a stable softmax.
Hint
np.vstack([cache["k"], k[None]]) appends a row to a table; k[None] turns the vector of numbers into a table of one row.