[MLX] Add off-graph KV-cache flat runtime - #21501
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21501
Note: Links to docs will display an error until the docs builds have been completed. ⏳ No Failures, 17 PendingAs of commit 011bd32 with merge base 43cb2b2 ( This comment was automatically generated by Dr. CI and updates every 15 minutes. |
This PR needs a
|
| causal: bool = false; | ||
| } | ||
|
|
||
| table UpdateAndAttendNode { |
There was a problem hiding this comment.
Didn't the op have an output dtype?
| // write is a slice_update run, a read a [0, len) slice. H/D inferred on write. | ||
| class FlatPool { | ||
| public: | ||
| void write(int start, const Tensor& kv, int capacity, StreamOrDevice s) { |
| // write is a slice_update run, a read a [0, len) slice. H/D inferred on write. | ||
| class FlatPool { | ||
| public: | ||
| void write(int start, const Tensor& kv, int capacity, StreamOrDevice s) { |
There was a problem hiding this comment.
Why is capacity a property of write?
| buf_ = ::mlx::core::slice_update(*buf_, kv, to_shape(lo), to_shape(hi), s); | ||
| } | ||
|
|
||
| Tensor read(int len, StreamOrDevice s) const { |
There was a problem hiding this comment.
Is return type Tensor or ::mlx::array?
There was a problem hiding this comment.
Tensor is the runtime's existing alias for ::mlx::core::array (in MLXExecutor.h), so I used it as in there.
88761ae to
9321153
Compare
|
Recent changes:
|
| : cache::SequenceCache(cfg) { | ||
| const ::mlx::core::Dtype dt = cfg.kv_dtype | ||
| ? resolve_dtype(static_cast<int8_t>(*cfg.kv_dtype)) | ||
| : ::mlx::core::float16; |
There was a problem hiding this comment.
Raise if dtype not specified.
| // position vector is tiny). Cast to int32 so an int64 position also works. | ||
| static int read_start(const Tensor& position, StreamOrDevice s) { | ||
| Tensor p = ::mlx::core::astype(position, ::mlx::core::int32, s); | ||
| ::mlx::core::eval(p); |
There was a problem hiding this comment.
I'm quite worried about this. It breaks async evalution.
Can we lift this logic to exec_update_and_attend at least and pass an int down. I think it'll be easier to correct at that level if needed.
9321153 to
4d88209
Compare
| #include "MLXFlatCache.h" | ||
|
|
||
| #include <mlx/mlx.h> | ||
|
|
There was a problem hiding this comment.
We also need something to run the c++ test in CI. You might need to modify mlx.yml
ad06aae to
b27b89c
Compare
b27b89c to
011bd32
Compare
Summary
Adds the MLX runtime for the off-graph KV cache: a flat (full-history) cache plus the update_and_attend op handler, wiring the neutral SequenceCache/FlatPolicy bookkeeping to real MLX tensors. The graph carries a cache-agnostic UpdateAndAttendNode — no cache operand, since the cache is off-graph runtime state keyed by layer_id. At runtime the handler asks the cache for the K/V window and mask kind (AttendSpec) and calls SDPA: the cache owns KV byte movement + attention semantics, the handler owns q/scale + the SDPA call. Single-sequence, fp16, full-history; the per-query mask stays in the backend as MLX's fused "causal"/none (no materialized tensor).
Files
Testing
C++ op-level test: drives MLXFlatCache::update_and_fetch directly and attends the returned AttendSpec exactly as the handler does, comparing to MLX SDPA over the full K/V history the cache should have assembled, prefill (Causal), decode (None), and the capacity-reject path. Runs on Apple Silicon (MLX SDPA needs the Metal backend). Built and ran via the standard flow, passes:
cmake --preset mlx-release -DEXECUTORCH_BUILD_TESTS=ON
cmake --build cmake-out --target mlx_flat_cache_test
ctest --test-dir cmake-out -R mlx_flat_cache --output-on-failure