-
Notifications
You must be signed in to change notification settings - Fork 133
Cache reuse and cache fixes #188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
christian-lms
wants to merge
40
commits into
lmstudio-ai:main
Choose a base branch
from
christian-lms:christian/cache_reuse_again
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
f11604b
loop record generated token
christian-lms 64113b3
shifting kv cache
christian-lms 19cde88
override another method to use rope shift
christian-lms f4822f0
add testing asserts
christian-lms e297092
warn
christian-lms ee316db
move cache into a separate file
christian-lms 3df5d31
begin raw unit tests
christian-lms 514b6c5
initial uncommented tests
christian-lms 3f64566
manual rope stuff
christian-lms 22d8a83
pipe in n_keep
christian-lms d165867
a few more cache wrapper tests
christian-lms 199d231
ruff formatting lmao
christian-lms 8dcb82d
record_generated_token test and fix
christian-lms 56d34e0
prelim reuse code
christian-lms 3741509
maybe reuse unit test
christian-lms a677f86
code reuse!
christian-lms 77e523c
cache shift test comments
christian-lms 2a8855a
stop rope shifting values and set keep
christian-lms 447a134
cache is a list, and exclude tokens in the right place
christian-lms 16fc7a1
same for tests
christian-lms 85f2241
apply that to tests too oops
christian-lms 6ac8d2f
decouple from rotatingkvcache since so much of it was rewritten anywa…
christian-lms d124c0e
working reuse test
christian-lms 8ac2bae
cache offsets ooooooooooooooooooooops
christian-lms e373181
refactor trim/temporal order internal interfaces to operate on both k…
christian-lms 4b938be
more test fixes
christian-lms d7c4ce7
refactor tests
christian-lms 5d60f13
technically if you ran this it would work
christian-lms 642a8c3
Merge branch 'lmstudio-ai:main' into christian/cache_reuse_again
christian-lms 9c378e6
properly works now (i think)
christian-lms aed67a9
try to remove rope
christian-lms 1b9af40
simplify cache again
christian-lms a1521d8
more reductionism
christian-lms dd205ba
remove prints
christian-lms a740335
??? oops
christian-lms 349b5a8
final fixes for now
christian-lms 1c4cf24
more fixes
christian-lms bf66e2b
make linter happy
christian-lms 3d51d58
fix trim
christian-lms 18b6dc3
extra tests (in progress)
christian-lms File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| from typing import List, Optional, Any | ||
|
|
||
| from mlx_lm.models.cache import RotatingKVCache, KVCache | ||
| import mlx.core as mx | ||
| import mlx.nn as nn | ||
|
|
||
|
|
||
| class ShiftingKVCache(RotatingKVCache): | ||
| def __init__(self, max_size=256, keep=0, step=256): | ||
| self.reuse_queue = [] | ||
| super().__init__(max_size=max_size, keep=keep, step=step) | ||
|
|
||
| def reuse_section( | ||
| self, write_start_idx: int, reuse_start_idx: int, reuse_length: int | ||
| ) -> None: | ||
| # queue for reuse: everything is done in one pass at the end in do_reuse | ||
| self.reuse_queue.append((write_start_idx, reuse_start_idx, reuse_length)) | ||
|
|
||
| def do_reuse(self) -> None: | ||
| if not self.reuse_queue: | ||
| return | ||
|
|
||
| # just in case maybe | ||
| self.keys = self._temporal_order(self.keys) | ||
| self.values = self._temporal_order(self.values) | ||
|
|
||
| # just in case, sort in write order | ||
| self.reuse_queue.sort(key=lambda x: x[0]) | ||
|
|
||
| key_segments = [] | ||
| value_segments = [] | ||
| current_pos = 0 | ||
|
|
||
| for write_start_idx, reuse_start_idx, reuse_length in self.reuse_queue: | ||
| # add any gap before this write position | ||
| if current_pos < write_start_idx: | ||
| key_segments.append(self.keys[..., current_pos:write_start_idx, :]) | ||
| value_segments.append(self.values[..., current_pos:write_start_idx, :]) | ||
|
|
||
| reuse_end_idx = reuse_start_idx + reuse_length | ||
| current_pos = write_start_idx + reuse_length | ||
|
|
||
| key_segments.append(self.keys[..., reuse_start_idx:reuse_end_idx, :]) | ||
| value_segments.append(self.values[..., reuse_start_idx:reuse_end_idx, :]) | ||
|
|
||
| self.keys = mx.concatenate(key_segments, axis=2) | ||
| self.values = mx.concatenate(value_segments, axis=2) | ||
|
|
||
| # clean up | ||
| self.reuse_queue = [] | ||
| self._idx = self.keys.shape[2] | ||
| self.offset = self.keys.shape[2] | ||
|
|
||
| def trim(self, n) -> int: | ||
| # trim must not respect keep | ||
| n = min(self.offset, n) | ||
| if n <= 0: | ||
| return 0 | ||
|
|
||
| # put us back into the state before the circular buffer is full | ||
| self.keys = self._temporal_order(self.keys) | ||
| self.values = self._temporal_order(self.values) | ||
|
|
||
| new_length = max(self.keys.shape[2] - n, 0) | ||
| self.keys = self.keys[..., :new_length, :] | ||
| self.values = self.values[..., :new_length, :] | ||
|
|
||
| self.offset = new_length | ||
| self._idx = new_length | ||
| return n | ||
|
|
||
| def set_keep(self, keep): | ||
| # kv must be in temporal order, else we will keep the wrong thing | ||
| if self.keys is not None: | ||
| self.keys = self._temporal_order(self.keys) | ||
| if self.values is not None: | ||
| self.values = self._temporal_order(self.values) | ||
| self.keep = keep | ||
|
|
||
| def is_trimmable(self) -> bool: | ||
| return True | ||
|
|
||
|
|
||
| def make_prompt_cache( | ||
| model: nn.Module, | ||
| max_kv_size: Optional[int] = None, | ||
| keep: int = 4, | ||
| ) -> List[Any]: | ||
| """ | ||
| Construct the model's cache for use in generation. | ||
| This function will defer the cache construction to the model if it has a | ||
| ``make_cache`` method, otherwise it will make a default KV cache. | ||
| Args: | ||
| model (nn.Module): The language model. | ||
| max_kv_size (Optional[int]): If provided and the model does not have a | ||
| ``make_cache`` method, a ``ShiftingKVCache`` is used with a maximum | ||
| size of ``max_kv_size`` | ||
| """ | ||
| if hasattr(model, "make_cache"): | ||
| return model.make_cache() | ||
| num_layers = len(model.layers) | ||
| if max_kv_size is not None: | ||
| return [ | ||
| ShiftingKVCache(max_size=max_kv_size, keep=keep) for _ in range(num_layers) | ||
| ] | ||
| else: | ||
| return [KVCache() for _ in range(num_layers)] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List, Optionalare deprecated for years ....