Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Optimizing LLM Inference with Caching Strategies

Hello! Welcome to the next lesson in our module on LLM Operations and Security.

In our previous lesson, we explored quantization, a technique for making models smaller by reducing the numerical precision of their weights. This is a static optimization—it shrinks the model file itself, making it easier to store and load onto hardware.

Today, we shift our focus to a dynamic optimization that speeds up the process of generating text token by token. This lesson addresses the learning outcome: Describe caching strategies (e.g., KV caching) to optimize LLM inference speed.

We'll investigate why the standard autoregressive generation process is inefficient and how a clever caching mechanism, known as the KV cache, dramatically accelerates it. We'll also explore the trade-offs this introduces and the advanced strategies used to manage them.

1. The Inefficiency of Autoregressive Generation

As we learned in Module 1, decoder-only models like GPT generate text autoregressively—one token at a time. To generate the next token, the model needs to consider the entire sequence of preceding tokens (the prompt plus any tokens it has already generated).

This process involves passing the full sequence through the model's transformer layers. A key computation within each self-attention block is the creation of the Query (Q), Key (K), and Value (V) vectors for every token in the sequence. The model then uses the Query of the last token to attend to the Keys of all tokens to produce the final output.

Herein lies a massive inefficiency: to generate each new token, the model re-calculates the Key and Value vectors for all the previous tokens, even though it just calculated them in the previous step. This redundant work grows with every new token generated, making the overall process quadratically slow with respect to the sequence length.

To see this inefficiency explained with clear examples and visuals, let's watch the first part of the following video.

KV Cache in 15 min

The video 'KV Cache in 15 min' by Zachary Huang provides an excellent, developer-focused breakdown of this problem. It starts by highlighting the redundant computation hidden inside the self-attention mechanism and uses a simple example to illustrate the 'triangle of waste'.

Watch from the beginning to 06:41. Focus on understanding why the simple, stateless design leads to recomputing Key and Value vectors and how this results in O(T^2) complexity, where T is the sequence length.

2. The Solution: Key-Value (KV) Caching

The solution to this redundant computation is conceptually simple: if the Key and Value vectors for past tokens don't change, why not save them? This is the core idea behind KV caching.

Instead of recomputing everything from scratch, the model stores the K and V vectors for each token after they are computed. In the next generation step, the model only needs to:

  1. Process the single newest token to compute its Q, K, and V vectors.
  2. Retrieve the cached K and V vectors from all previous steps.
  3. Concatenate the old K/V vectors with the new K/V vectors to form the full context.
  4. Use the new token's Q vector to attend to the full history of K/V vectors.
  5. Update the cache with the new, longer set of K/V vectors for the next step.

This transforms the generation process. The initial processing of the prompt, called the prefill or prompt processing phase, is still computationally intensive as the K and V vectors for the entire input must be computed for the first time. However, each subsequent token generation step, known as the decode or generation phase, becomes much faster, as it only involves computation for a single new token.

KV Caching Computation Process
This diagram illustrates the two main phases. In the 'Prefill' step, the K and V vectors for the initial prompt are computed and stored in the cache. In subsequent 'Decode' steps, these cached vectors are reused, and only the K and V for the newest token are computed.

This caching mechanism changes the computational complexity from quadratic to linear, enabling the fast, streaming responses you see in applications like ChatGPT.

Let's continue with the video to see how this efficient workflow is implemented.

KV Cache in 15 min

The same video now explains the new, efficient workflow with caching. It revisits the 'triangle of waste' and shows how it's eliminated, transforming the computation into a constant-work-per-token model.

Watch from 06:41 to the end (15:50). Pay close attention to how the input to the model changes (from the full sequence to just the new token) and how the cache is passed in and out of the attention block. The trace of tensor shapes provides a concrete view of the data flow, which should resonate with your software engineering background.

3. The Trade-Off: Memory Consumption

KV caching provides a significant speedup, but it comes at the cost of memory. The cache stores the Key and Value vectors for every token in the context, for every attention head, and for every layer of the model. As the sequence length and batch size grow, the memory required for the cache can become enormous, often exceeding the memory needed for the model weights themselves.

The size of the KV cache can be calculated as follows:

The 2 at the beginning accounts for storing both Keys and Values. For a large model with a long context window, this can easily reach hundreds of gigabytes, making inference memory-bound rather than compute-bound.

This memory overhead is a critical challenge, and several strategies have been developed to manage it.

4. Strategies for Managing the KV Cache

To mitigate the memory cost of the KV cache, researchers and engineers have developed several architectural and memory management optimizations. Let's explore the most important ones.

Optimizing LLM Inference: Managing the KV Cache

The article 'Optimizing LLM Inference: Managing the KV Cache' by Aalok Patwa provides a clear, comprehensive overview of these advanced strategies. We'll use it to understand the key techniques used in modern LLMs.

Please read the sections from 'The problem with the KV Cache: memory' through to the end of 'PagedAttention'. This will introduce you to four key strategies for managing the cache.

Here is a summary of the strategies covered in the article:

  • Multi-Query Attention (MQA): In standard Multi-Head Attention, each "head" has its own Q, K, and V projection weights. MQA modifies this by having multiple query heads but sharing a single key and value head across all of them. This drastically reduces the size of the K and V tensors that need to be cached, shrinking the cache size by a factor equal to the number of attention heads.

  • Grouped-Query Attention (GQA): GQA is a compromise between standard MHA and MQA. Instead of sharing one K/V head across all query heads, it shares one K/V head across a small group of query heads. This provides a better balance between model performance and memory savings. Many modern models, including Llama 3 and Mistral, use GQA.

  • Sliding-Window Attention (SWA): This approach tackles the seq_length component of the memory cost. Instead of allowing each token to attend to all previous tokens, SWA restricts the attention to a fixed-size window of recent tokens (e.g., the last 4096 tokens). This makes the KV cache size constant instead of growing linearly with the sequence length. While it limits direct access to distant tokens in a single layer, information can still propagate through the layers.

  • PagedAttention: Inspired by virtual memory and paging in operating systems, PagedAttention is a memory management technique. It allocates the KV cache in non-contiguous blocks of memory called "pages." This avoids memory fragmentation that occurs when trying to allocate large, contiguous blocks for each request, leading to much higher memory utilization and system throughput. This is the core innovation behind the popular vLLM inference server.

Conclusion

In this lesson, we've dissected one of the most critical optimizations for LLM inference. While quantization reduces a model's static footprint, KV caching accelerates its dynamic generation process.

Key Takeaways:

  • The Problem: Autoregressive generation without caching is inefficient (O(T^2)) because it repeatedly recomputes Key and Value vectors for the entire context at every step.
  • The Solution (KV Caching): By storing (caching) the K and V vectors, each generation step only needs to compute these for the newest token, reducing complexity to linear time (O(T)).
  • The Trade-Off: This speedup comes at the cost of significant memory usage, as the cache can become very large for long sequences.
  • Management Strategies: Techniques like MQA and GQA reduce the number of K/V heads to shrink the cache. SWA limits the cache size by using a fixed attention window. PagedAttention improves memory efficiency by using a virtual memory system for the cache.

In our next lesson, we'll shift from performance optimization to another crucial aspect of LLM operations: safety. We will explore how to implement an input/output moderation filter to handle inappropriate content, a vital step in building responsible AI applications.

Can't find a good explanation? Sign up and we'll make it for you

Sign up