# Lookaheadkv Predictive Cache Eviction

> Evict unnecessary KV cache entries using trainable lookahead tokens and LoRA modules that predict token importance without drafting responses. Achieve 14.5× speedup over draft-based methods with 2% eviction overhead.

- Skill: `adu2021/lookaheadkv-predictive-cache-eviction` (Agent Skill)
- Install (CLI): `npx skillmds@latest add adu2021/lookaheadkv-predictive-cache-eviction`
- Raw SKILL.md: https://api.skillmd.com/api/skills/adu2021/lookaheadkv-predictive-cache-eviction/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: adu2021 (https://skillmd.com/u/adu2021)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/adu2021/lookaheadkv-predictive-cache-eviction

---


# Technique: Lookahead LoRA Modules for Predictive Token Importance

Long-context inference requires managing KV cache efficiently. Standard approaches either cache everything (wasteful) or draft responses to predict importance (expensive). LookaheadKV predicts token importance using lightweight learnable modules: lookahead soft tokens with selective LoRA adapters that learn to forecast attention patterns without generating responses.

This sidesteps the accuracy-latency tradeoff of existing methods through cheap, predictive importance scoring.

## Core Concept

LookaheadKV operates through three mechanisms:

1. **Lookahead Soft Tokens**: Trainable tokens appended during prefill to observe upcoming attention patterns
2. **Lookahead LoRA Modules**: Selective adapters that learn richer representations for lookahead tokens only
3. **Importance Scoring**: Predict token importance from lookahead outputs without drafting

This achieves importance prediction comparable to actual generation at negligible overhead (<2%).

## Architecture Overview

- **Lookahead tokens**: Trainable soft embeddings (typically 4-8 tokens)
- **LoRA adapters**: Low-rank modifications activating selectively
- **Importance predictor**: Scores tokens based on lookahead outputs
- **KV cache manager**: Stores and evicts based on predictions
- **Training objective**: Match importance to ground-truth attention

## Implementation Steps

### Step 1: Initialize Lookahead Tokens and LoRA Modules

Create trainable components for predictive importance estimation.

```python
import torch
import torch.nn as nn

class LookaheadTokens(nn.Module):
    def __init__(self, num_lookahead=8, hidden_dim=4096):
        super().__init__()
        self.num_lookahead = num_lookahead
        self.hidden_dim = hidden_dim

        # Trainable lookahead embeddings
        self.lookahead_embeddings = nn.Parameter(
            torch.randn(num_lookahead, hidden_dim)
        )

    def forward(self, input_sequence):
        """
        Append lookahead tokens to input sequence.

        input_sequence: (batch, seq_len, hidden_dim)
        returns: (batch, seq_len + num_lookahead, hidden_dim)
        """
        batch_size = input_sequence.shape[0]

        # Broadcast lookahead embeddings to batch
        lookahead = self.lookahead_embeddings.unsqueeze(0).expand(
            batch_size, -1, -1
        )  # (batch, num_lookahead, hidden_dim)

        # Concatenate with input
        augmented = torch.cat([input_sequence, lookahead], dim=1)

        return augmented
```

### Step 2: Lookahead LoRA Adapters

Selective low-rank modules that activate only for lookahead tokens.

```python
class LookaheadLoRA(nn.Module):
    def __init__(self, hidden_dim=4096, lora_rank=16, num_lookahead=8):
        super().__init__()
        self.hidden_dim = hidden_dim
        self.lora_rank = lora_rank
        self.num_lookahead = num_lookahead

        # LoRA projections
        self.lora_down = nn.Linear(hidden_dim, lora_rank)
        self.lora_up = nn.Linear(lora_rank, hidden_dim)

        # Gate for selective activation (lookahead tokens only)
        self.selection_gate = nn.Parameter(
            torch.zeros(1, 1, 1)  # Will be filled during forward
        )

    def forward(self, hidden_states, lookahead_mask):
        """
        Apply LoRA modifications selectively to lookahead tokens.

        hidden_states: (batch, seq_len + num_lookahead, hidden_dim)
        lookahead_mask: (batch, seq_len + num_lookahead) boolean mask
        """
        # Project down
        projected = self.lora_down(hidden_states)  # (batch, seq, lora_rank)

        # Project up
        adapted = self.lora_up(projected)  # (batch, seq, hidden_dim)

        # Apply gate: only modify lookahead positions
        gate = lookahead_mask.unsqueeze(-1).float()  # (batch, seq, 1)
        modified = hidden_states + gate * adapted * 0.01  # Scale down LoRA contribution

        return modified
```

### Step 3: Train Importance Prediction

Learn to predict token importance from lookahead outputs.

```python
class ImportancePredictorTraining:
    def __init__(self, model, lookahead_tokens, lora_adapters):
        self.model = model
        self.lookahead_tokens = lookahead_tokens
        self.lora_adapters = lora_adapters

    def train_step(self, input_ids, target_length=32000):
        """
        Train lookahead components to predict importance.

        Importance ground truth: attention head sums across generated response.
        """
        # Forward pass with lookahead
        augmented_input = self.lookahead_tokens(self.model.embed(input_ids))

        # Apply LoRA
        lookahead_mask = self._create_lookahead_mask(augmented_input)
        adapted = self.lora_adapters(augmented_input, lookahead_mask)

        # Run through model layers
        hidden_states = adapted
        for layer in self.model.layers:
            hidden_states = layer(hidden_states)

        # Extract lookahead token outputs
        lookahead_outputs = hidden_states[:, -self.lookahead_tokens.num_lookahead:, :]

        # Predict importance scores
        importance_predictions = self._predict_importance(lookahead_outputs)

        # Generate full response to get ground-truth importance
        with torch.no_grad():
            full_generation = self.model.generate(
                input_ids,
                max_new_tokens=100
            )

        # Compute ground-truth importance
        ground_truth_importance = self._compute_ground_truth_importance(
            full_generation,
            target_length
        )

        # KL divergence loss
        loss = torch.nn.functional.kl_div(
            torch.log_softmax(importance_predictions, dim=-1),
            torch.softmax(ground_truth_importance, dim=-1),
            reduction='batchmean'
        )

        return loss

    def _create_lookahead_mask(self, hidden_states):
        """Mark which positions are lookahead tokens."""
        batch_size, seq_len = hidden_states.shape[:2]
        num_lookahead = self.lookahead_tokens.num_lookahead

        mask = torch.zeros(batch_size, seq_len, dtype=torch.bool)
        mask[:, -num_lookahead:] = True

        return mask

    def _predict_importance(self, lookahead_outputs):
        """Predict token importance from lookahead."""
        # MLP head: (num_lookahead, hidden_dim) -> (seq_len, 1)
        predictor = nn.Sequential(
            nn.Linear(lookahead_outputs.shape[-1], 256),
            nn.ReLU(),
            nn.Linear(256, lookahead_outputs.shape[1])  # Predict importance for all tokens
        )

        predictions = predictor(lookahead_outputs)
        return predictions

    def _compute_ground_truth_importance(self, generation, target_length):
        """Compute importance from actual attention patterns."""
        # Sum attention heads across generated response
        importance = torch.zeros(target_length)
        # (Simplified; real implementation aggregates attention weights)
        return importance
```

### Step 4: Efficient Inference with Importance Eviction

Use predicted importance scores to evict low-importance tokens.

```python
class KVCacheWithImportanceEviction:
    def __init__(self, model, max_cache_size=32000):
        self.model = model
        self.max_cache_size = max_cache_size
        self.cache = {}

    def forward_with_eviction(
        self,
        input_ids,
        importance_predictor,
        eviction_ratio=0.5
    ):
        """
        Forward pass with KV cache eviction based on predicted importance.
        """
        batch_size, seq_len = input_ids.shape

        # Get predicted importance scores
        importance_scores = importance_predictor(input_ids)

        # Determine which tokens to keep
        num_to_keep = int(seq_len * (1 - eviction_ratio))
        _, keep_indices = torch.topk(importance_scores, num_to_keep, dim=1)

        # Filter inputs and cache
        filtered_input = input_ids[:, keep_indices]

        # Forward pass on filtered tokens
        output = self.model(filtered_input)

        return output

    def profile_eviction_overhead(self, batch_size=1, seq_len=32000):
        """Measure eviction pipeline overhead."""
        import time

        # Lookahead token processing
        start = time.time()
        lookahead_time = time.time() - start

        # LoRA adaptation
        start = time.time()
        lora_time = time.time() - start

        # Importance prediction
        start = time.time()
        pred_time = time.time() - start

        total_overhead = (lookahead_time + lora_time + pred_time) / (
            batch_size * seq_len
        )

        print(f"Eviction overhead: {total_overhead * 100:.2f}% per token")

        return total_overhead
```

### Step 5: Integration with Long-Context Inference

End-to-end pipeline for efficient long-context generation.

```python
def generate_with_lookaheadkv(
    model,
    input_ids,
    max_new_tokens=100,
    max_cache_size=32000,
    eviction_ratio=0.5,
    importance_predictor=None
):
    """
    Generate with efficient KV cache management.
    """
    cache_manager = KVCacheWithImportanceEviction(model, max_cache_size)

    generated_tokens = []
    current_input = input_ids

    for step in range(max_new_tokens):
        # Check if cache exceeds limit
        if current_input.shape[1] > max_cache_size:
            # Evict low-importance tokens
            if importance_predictor:
                current_input = cache_manager.forward_with_eviction(
                    current_input,
                    importance_predictor,
                    eviction_ratio=eviction_ratio
                )

        # Generate next token
        output = model(current_input)
        next_token = torch.argmax(output[:, -1, :], dim=-1, keepdim=True)

        generated_tokens.append(next_token)
        current_input = torch.cat([current_input, next_token], dim=1)

    # Decode generated tokens
    generated_ids = torch.cat(generated_tokens, dim=1)
    generated_text = model.tokenizer.decode(generated_ids[0])

    return generated_text
```

## Practical Guidance

**When to Use:**
- Long-context inference (20K+ tokens)
- Latency-critical applications (sub-100ms targets)
- Scenarios with budget constraints on memory
- Tasks where token importance is predictable (e.g., retrieval-augmented)

**When NOT to Use:**
- Short contexts (<10K) where cache overhead dominates
- Tasks requiring full attention (all tokens equally important)
- Extremely tight memory constraints even after eviction

**Hyperparameter Tuning:**
- **num_lookahead**: 4-16 tokens; more accurate but higher overhead
- **lora_rank**: 8-32; balance expressiveness and efficiency
- **eviction_ratio**: 0.3-0.7; higher ratio more aggressive
- **lookahead LoRA scale**: 0.01-0.1; smaller ⟹ less disruption to normal tokens

**Common Pitfalls:**
- Lookahead token gradient flow disrupting main model
- LoRA gates allowing modification of non-lookahead tokens
- Importance predictions poorly calibrated (check KL divergence)
- Insufficient training of lookahead components

## Reference

[LookaheadKV paper on arXiv](https://arxiv.org/abs/2603.10899)

