Add functional update_and_attend KV-cache op + reference cache#21112
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21112
Note: Links to docs will display an error until the docs builds have been completed. ❗ 1 Active SEVsThere are 1 currently active SEVs. If your PR is affected, please view them below: ✅ No FailuresAs of commit 265ed7d with merge base 3ba685e ( This comment was automatically generated by Dr. CI and updates every 15 minutes. |
This PR needs a
|
| ], | ||
| ) | ||
|
|
||
| fbcode_target(_kind = runtime.python_binary, |
There was a problem hiding this comment.
Looks like you need to rebase?
| from executorch.extension.llm.custom_ops.op_update_and_attend_reference import attend | ||
|
|
||
| _REGISTRY: Dict[str, object] = {} | ||
| _CURRENT_KEY: Optional[str] = None |
There was a problem hiding this comment.
I know this is just a python reference, but maybe wrap this in a CacheRegistry class for neatness?
| scale: float, | ||
| out_dtype: torch.dtype, | ||
| ) -> torch.Tensor: | ||
| return torch.empty_like(q, dtype=out_dtype) |
There was a problem hiding this comment.
Is this the right shape? Shouldn't final dim be based on v? See https://docs.pytorch.org/docs/2.13/generated/torch.nn.functional.scaled_dot_product_attention.html
There was a problem hiding this comment.
You are right, attn_weight is [B, H, q_len, k_len] and value is [B, H, k_len, D_v], so attn_weight @ value is [B, H, q_len, D_v].
|
|
||
|
|
||
| @torch.library.custom_op("kvcache::update_and_attend", mutates_args=()) | ||
| def update_and_attend( |
There was a problem hiding this comment.
Can we have a doc string describing shape of each arg?
For example, the op should natively handle GQA, so make sure q/k/v shapes are described as such
| k = k.repeat_interleave(rep, dim=1) | ||
| v = v.repeat_interleave(rep, dim=1) | ||
|
|
||
| qf = q.to(torch.float32) |
There was a problem hiding this comment.
Why not use F.scaled_dot_product_attention?
| def _export(self, seq_len): | ||
| x = torch.randn(1, seq_len, self.hidden) | ||
| pos = _positions(0, seq_len) | ||
| idx = torch.arange(seq_len, dtype=torch.long) |
There was a problem hiding this comment.
Can we run functionalization on the graph? Sometimes export succeeds where functionalization fails (as an aside, ET always runs functionalization):
ep = ep.run_decompositions({})
| with self.subTest(sizing=sizing): | ||
| cache = ContiguousReferenceCache(self._config(sizing, cap)) | ||
| install_cache(self.cache_key, cache) | ||
| set_active(self.cache_key) |
There was a problem hiding this comment.
What is difference between set_active and install?
There was a problem hiding this comment.
install registers a cache under a key once (at creation), while active selects which installed cache the op uses for a given forward. They look identical with one cache, but distinct with multi-sequence, where we install each once and switch active per step.
| @@ -0,0 +1,224 @@ | |||
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |||
There was a problem hiding this comment.
Make sure this is run by the unittest job in CI (look at raw logs to confirm)
| @@ -0,0 +1,167 @@ | |||
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |||
There was a problem hiding this comment.
I take it this is just an example implementation?
At runtime we do not have to use things like AttendSpec for implementation?
There was a problem hiding this comment.
Correct, and for the contiguous cases here the mask is always causal so it is not really functional now, I put it to use later for explicit but I can drop it for simplicity.
c47a9f3 to
8c72c6b
Compare
|
Looks good! Thinking ahead to where c++ stuff will live, maybe it's better to move these files to a dedicated export/llm/cache folder. Also mark as experimental |
Summary
Adds a neutral kvcache::update_and_attend custom op for KV-cache attention. The op is functional to the tracer with the cache held off-graph via a process-global registry, so the exported .pte does not bake in cache size etc. Ships an eager reference cache (ContiguousReferenceCache) with runtime-selectable static/dynamic sizing and a hard capacity bound.
(install_cache/set_active/uninstall_cache).
Test plan
pytest -v extension/llm/custom_ops/test_update_and_attend.py
Verifies the exported graph is functional (no buffer inputs/mutations, one op call per layer) and that outputs match cacheless causal attention for prefill and incremental decode under both static and dynamic sizing.