Repository navigation
Fix several bugs in cache_implementation=static
#46446
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
Changes from all commits
e5aff89
90b0b61
994e1d9
7b2aa4a
533d3d6
40ddb52
aa28be8
f1593a9
835e90f
58842f7
6ec3342
78035b8
bf237fd
4ab7a91
8bd037d
4c41a82
6f39ad1
736803a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -173,6 +173,12 @@ class GenerationConfig(PushToHubMixin): | |
| our [cache documentation](https://huggingface.co/docs/transformers/en/kv_cache) for further information. | ||
| cache_config (`dict`, *optional*, default to `None`): | ||
| Arguments used in the key-value cache class can be passed in `cache_config`. | ||
| max_cache_len (`int`, *optional*): | ||
| Only used with static caches (`cache_implementation` set to `"static"` or `"offloaded_static"`). | ||
| Pre-sizes the cache to this length instead of the current call's `max_length`. Set it once to the | ||
| largest call you expect so that repeated `generate()` calls with a longer prompt or a larger | ||
| `max_new_tokens` (up to this ceiling) reuse the same cache instead of triggering a reallocation and a | ||
| `torch.compile` recompilation. | ||
|
Comment on lines
+176
to
+181
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i am surprise this is not in generation config |
||
|
|
||
| > Parameters for manipulation of the model output logits | ||
|
|
||
|
|
@@ -388,6 +394,7 @@ def __init__(self, **kwargs): | |
| self.use_cache = kwargs.pop("use_cache", None) | ||
| self.cache_implementation = kwargs.pop("cache_implementation", None) | ||
| self.cache_config = kwargs.pop("cache_config", None) | ||
| self.max_cache_len = kwargs.pop("max_cache_len", None) | ||
|
|
||
| # Parameters for manipulation of the model output logits | ||
| self.temperature = kwargs.pop("temperature", None) | ||
|
|
@@ -654,6 +661,11 @@ def validate(self, strict=False, user_set_attributes: set[str] | None = None): | |
| f"Invalid `cache_implementation` ({self.cache_implementation}). Choose one of: " | ||
| f"{valid_cache_implementations}" | ||
| ) | ||
| if self.max_cache_len is not None and self.cache_implementation not in ALL_STATIC_CACHE_IMPLEMENTATIONS: | ||
| logger.warning_once( | ||
| f"`max_cache_len` is only used with static caches ({STATIC_CACHE_IMPLEMENTATIONS}); it will be " | ||
| f"ignored with `cache_implementation={self.cache_implementation!r}`." | ||
| ) | ||
| # 1.3. Performance attributes | ||
| if self.compile_config is not None and not isinstance(self.compile_config, CompileConfig): | ||
| raise ValueError( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1784,8 +1784,33 @@ def _prepare_generation_config( | |
|
|
||
| return generation_config, model_kwargs | ||
|
|
||
| def _get_static_cache_init_shape(self: "GenerativePreTrainedModel") -> tuple[int, int] | None: | ||
| """ | ||
| Returns the per-rank `(num_heads, head_dim)` to eagerly initialize a `StaticCache`, with the head count sharded | ||
| for tensor parallelism. Returns `None` when the cache cannot be early initialized. | ||
| """ | ||
| if hasattr(self, "hf_device_map") and len(set(self.hf_device_map.values())) > 1: | ||
| # The model layers are on different devices | ||
| return None | ||
| text_config = self.config.get_text_config(decoder=True) | ||
| tp_size = getattr(self, "_tp_size", None) or 1 | ||
| num_key_value_heads = getattr(text_config, "num_key_value_heads", None) or text_config.num_attention_heads | ||
| if num_key_value_heads % tp_size != 0: | ||
| # The model cannot be evenly sharded by head | ||
| return None | ||
| if getattr(text_config, "qk_head_dim", None) is not None: | ||
| # MLA models have distinct key (`qk_head_dim`) and value (`v_head_dim`) sizes. | ||
| return None | ||
| head_dim = getattr(text_config, "head_dim", None) or text_config.hidden_size // text_config.num_attention_heads | ||
| return num_key_value_heads // tp_size, head_dim | ||
|
Comment on lines
+1797
to
+1805
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this does not necessarily scale well with DSv4 for example and
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added yet another exclusion |
||
|
|
||
| def _prepare_static_cache( | ||
| self: "GenerativePreTrainedModel", cache_implementation: str, batch_size: int, max_cache_len: int, model_kwargs | ||
| self: "GenerativePreTrainedModel", | ||
| cache_implementation: str, | ||
| batch_size: int, | ||
| max_cache_len: int, | ||
| prefill_chunk_size: int | None, | ||
| model_kwargs, | ||
| ) -> Cache: | ||
| """ | ||
| Sets a cache for `generate`, that will persist across calls. A new cache will only be initialized a | ||
|
|
@@ -1831,6 +1856,19 @@ def _prepare_static_cache( | |
| "offloading": offload_cache, | ||
| } | ||
| self._cache = EncoderDecoderCache(self._cache, StaticCache(**cross_attention_cache_kwargs)) | ||
| elif prefill_chunk_size is not None: | ||
| # Chunked prefill compiles the prefill, so eagerly init the fresh cache to avoid a recompile next call | ||
| # (#46421). Skipped (-> lazy init) when it can't be initialized on a single device. | ||
| init_shape = self._get_static_cache_init_shape() | ||
| if init_shape is not None: | ||
| num_heads, head_dim = init_shape | ||
| self._cache.early_initialization( | ||
| batch_size=batch_size, | ||
| num_heads=num_heads, | ||
| head_dim=head_dim, | ||
| dtype=self.dtype, | ||
| device=self.device, | ||
| ) | ||
|
Comment on lines
+1859
to
+1871
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, looks like we actually miss most of the cases, as it will only work for 1 gpu setup. The better way to proceed I believe would be to simply force the 1st chunk to NOT use compile, so that the cache is properly initialized before compile. WDYT? I don't think it would harm performance at all in general to have a single forward not compiled
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's take a step back. The whole point of using chunked prefill and static cache in combination is to have fully static graphs for prefill and decode, regardless of the prompt length and max new tokens. This might look like a mild optimization for CUDA devices (would need proper benchmarks to confirm), but it is essential on devices that do not support dynamic shapes (Neuron and TPU), for which eager mode is just "fragmented compilation". |
||
| else: | ||
| self._cache.reset() | ||
| return self._cache | ||
|
|
@@ -1929,10 +1967,18 @@ def _prepare_cache_for_generation( | |
| f"and will be removed in v5.13. Please only use one of {STATIC_CACHE_IMPLEMENTATIONS}, " | ||
| "and the layer structure will be inferred automatically." | ||
| ) | ||
| # `max_cache_len` lets the static cache be sized for the worst case across calls, so that later calls | ||
| # with a longer prompt or a larger `max_new_tokens` (up to that ceiling) reuse the same cache instead of | ||
| # triggering a reallocation (and a `torch.compile` recompilation). Without it, the cache is sized to the | ||
| # current call's `max_length` only. See #46424. | ||
| if generation_config.max_cache_len is not None: | ||
| max_cache_length = max(max_cache_length, generation_config.max_cache_len) | ||
| cache_batch_size = max(generation_config.num_beams, generation_config.num_return_sequences) * batch_size | ||
| model_kwargs[cache_name] = self._prepare_static_cache( | ||
| cache_implementation=generation_config.cache_implementation, | ||
| batch_size=max(generation_config.num_beams, generation_config.num_return_sequences) * batch_size, | ||
| batch_size=cache_batch_size, | ||
| max_cache_len=max_cache_length, | ||
| prefill_chunk_size=generation_config.prefill_chunk_size, | ||
| model_kwargs=model_kwargs, | ||
| ) | ||
| elif generation_config.cache_implementation == "quantized": | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.