{"record":{"id":"e3a6013246c221ef","repo":"huggingface/transformers","slug":"linear-attention-layers-can-only-be-cropped-by-pas","errorCode":null,"errorMessage":"Linear attention layers can only be cropped by passing a negative int, to specify how many tokens to remove","messagePattern":"Linear attention layers can only be cropped by passing a negative int, to specify how many tokens to remove","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"src/transformers/cache_utils.py","lineNumber":978,"sourceCode":"        Calling this function will activate past state recording, meaning that a call to `update_conv_states` will\n        wait for a call to `crop` before restricting the size of the `conv_states` to `conv_kernel_size`, to be able\n        to retrieve previous full states.\n        \"\"\"\n        self.record_past = True\n\n    def crop(self, tokens_to_remove: int):\n        \"\"\"\n        Remove `tokens_to_remove` tokens from the current cache layer. This will also restrict the size of the cached states back to their\n        minimal working size, i.e. `conv_kernel_size`. This means that `crop(0)` will not necessarily always be a no-op, as it may\n        still remove useless states (i.e. states that are not needed for the next `forward`).\n        \"\"\"\n        if not self.record_past:\n            raise RuntimeError(\n                \"`crop` was called, but the current layer does not track past states. Call `activate_past_recording` before \"\n                \"`crop` to be able to rollback the cache.\"\n            )\n        if tokens_to_remove > 0:\n            raise RuntimeError(\n                \"Linear attention layers can only be cropped by passing a negative int, to specify how many tokens to remove\"\n            )\n        for i in range(self.number_of_states):\n            tokens_to_remove = abs(tokens_to_remove)\n            # In this case, simply restrict the size back to `conv_kernel_size` without cropping\n            if tokens_to_remove == 0:\n                self.conv_states[i] = self.conv_states[i][..., -self.conv_kernel_size[i] :]\n            # This both crop the last `tokens_to_remove`, as well as resize the conv states to `conv_kernel_size` as we never\n            # need more for the next forward\n            else:\n                self.conv_states[i] = self.conv_states[i][\n                    ..., -tokens_to_remove - self.conv_kernel_size[i] : -tokens_to_remove\n                ]\n\n    def get_max_length(self) -> int:\n        # LinearAttention layer have no sequence length dimension, so simply return -1 here\n        return -1\n","sourceCodeStart":960,"sourceCodeEnd":996,"githubUrl":"https://github.com/huggingface/transformers/blob/a597f974857b3d92939971296bc0deb93d33d780/src/transformers/cache_utils.py#L960-L996","documentation":"LinearAttentionCacheLayerMixin.crop() raises RuntimeError when tokens_to_remove is positive. Unlike attention KV caches where crop removes the last N positive tokens, linear attention layers expect a NEGATIVE int; the sign is a convention marking a linear-attention rollback and the code takes abs(tokens_to_remove) afterwards.","triggerScenarios":"Calling crop(positive_int) on a layer of type LinearAttentionCacheLayerMixin, e.g. cache.crop(2) instead of cache.crop(-2). Happens when code written for DynamicCache.crop(n) (which uses positive counts) is reused on a linear-attention cache.","commonSituations":"Shared helper code that crops caches uniformly across model types during generation; manually trimming cache state between decoding steps; porting DynamicCache examples to hybrid (attention + linear attention) models like Falcon-H1, Qwen3-Next or Zamba.","solutions":["Pass a negative value: cache.crop(-n) to remove the last n tokens of recorded history","Branch on cache type before cropping: use positive semantics for pure attention caches and negative for linear attention layers (Cache.crop dispatches per layer)","Check that activate_past_recording() was called, otherwise the sibling RuntimeError at the same site fires first"],"exampleFix":"// before\ncache.crop(2)\n\n// after\ncache.crop(-2)","handlingStrategy":"validation","validationCode":"def safe_crop(cache, n: int):\n    for layer in cache.layers:\n        if isinstance(layer, LinearAttentionCacheLayerMixin):\n            layer.crop(-abs(n))\n        else:\n            layer.crop(abs(n))\n\nsafe_crop(cache, 2)","typeGuard":"def is_linear_attention_layer(layer) -> bool:\n    return isinstance(layer, LinearAttentionCacheLayerMixin)","tryCatchPattern":"try:\n    cache.crop(n)\nexcept RuntimeError as e:\n    if \"negative int\" in str(e):\n        cache.crop(-abs(n))\n    else:\n        raise","preventionTips":["Adopt one internal convention: negative counts for linear attention rollback, positive for KV trimming","Wrap cache cropping in a helper that dispatches on layer type instead of calling crop uniformly","Read the layer's crop docstring before reusing DynamicCache idioms"],"tags":["cache","linear-attention","crop","sign-convention","runtimeerror"],"backgroundTag":null,"analyzedSha":"a597f974857b3d92939971296bc0deb93d33d780","analyzedAt":"2026-08-14T18:24:08.354Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}