Fixed-capacity KV cache
Lesson: KV Cache, Flash Attention & Inference OptimizationCheat sheetTrack: Transformers from scratchTrack: LLM inference
Problem
A decoder makes one token at a time. Without a cache, each new token computes the keys and values of the whole prefix again. A KV cache stores the key and the value of each step once. Then each new token needs only one query against the stored keys and values.
Task
Write a class KVCache and a function decode_step.
KVCache(max_len, d_k, d_v) makes an empty cache with room for max_len steps. It has these methods:
append(k, v)adds one or more steps after the steps that are already in the cache. A 1Dkof shape(d_k,)is one step. A 2Dkof shape(n, d_k)isnsteps. The same rule applies tov.keys()returns the stored keys, in the order of the appends, with shape(len, d_k).values()returns the stored values with shape(len, d_v).len(cache)returns the number of stored steps.
append raises ValueError in two cases:
- the new steps do not fit, that is,
len(cache) + n > max_len; kandvhave different numbers of steps.
When append raises, the cache does not change.
decode_step(q, cache) computes the attention of one query over the cache. Let K be cache.keys() and V be cache.values(). The scores are s = K q / \sqrt{d_k}. The weights are the softmax of s. The output is the weights times V.
Input and output
| Name | Type | Shape | Meaning |
|---|---|---|---|
max_len | int | the number of steps that the cache can hold | |
d_k, d_v | int | the size of one key and of one value | |
k | float array | (d_k,) or (n, d_k) | the keys of one or n new steps |
v | float array | (d_v,) or (n, d_v) | the values of the same steps |
q | float array | (d_k,) | the query of the current step |
cache | KVCache | a cache with at least one step |
appendreturnsNone.decode_stepreturns a tuple(output, weights).outputis a float array of shape(d_v,).weightsis a float array of shape(len(cache),).
Constraints
1 <= max_len <= 256and1 <= d_k, d_v <= 64.- Scores can be larger than 1000. The output must stay finite.
- The cache stores a copy of the data. A later change to the caller's
korvdoes not change the cache. - Do not change the input arrays.
- Use numpy only.
Examples
pythoncache = KVCache(4, 2, 2)
cache.append(np.array([[1.0, 0.0], [0.0, 1.0]]), np.array([[1.0, 2.0], [3.0, 4.0]]))
len(cache)
cache.append(np.array([1.0, 1.0]), np.array([5.0, 6.0]))
len(cache)
cache.values()text2
3
[[1. 2.]
[3. 4.]
[5. 6.]]pythonoutput, weights = decode_step(np.array([1.0, 0.0]), cache)textoutput
[3. 4.]
weights
[0.40111209 0.19777581 0.40111209]pythoncache.append(np.zeros((2, 2)), np.zeros((2, 2)))textValueError: the cache is fullAfter the error, len(cache) is still 3.
Hints
4 hintsSolution and notes
LockedPass every check with Submit to open the reference solution and the notes.
Run it on your computer
Clone the repository, install numpy and run the same checks in a terminal:
python3 scripts/practice/run.py kv-cache-append my_answer.py
python3 scripts/practice/run.py kv-cache-append my_answer.py --examplesStart my_answer.py from starter.py.
Your code is saved in this browser as you type. On a phone you can read and edit the code, but the checks are easier to use on a desktop.
Keyboard shortcuts
- Ctrl Enter
- Run the examples
- Ctrl Shift Enter
- Submit
- Tab / Shift Tab
- Indent or outdent the lines
- Esc then Tab
- Move the focus out of the editor
- Alt H
- Show the next hint
On a Mac, use Cmd in place of Ctrl.
Select Run to check the examples, or Submit to run every check.