Memory Augmentation › Memory Retrieval Mechanisms: From Soft to Efficient

Michael BrenndoerferUpdated July 8, 202512 min read
Language AI HandbookAll chapters
Part 18 of 67Long Context
Chapter 131Memory Augmentation › Memory Retrieval Mechanisms: From Soft to Efficient
Reading section 4 of 12 · Memory Retrieval Mechanisms: From Soft to Efficient · 12 minSection 4 of 12 · 12 min

Book contents

Section 4 of 12 · 12 min read

Memory Augmentation › Memory Retrieval Mechanisms: From Soft to EfficientOne section · Sections in this chapter4 / 12 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

The basic memory read operation we just implemented has an elegant mathematical formulation, but it harbors a serious scalability problem. Let's understand this issue deeply before exploring solutions.

The Soft Attention Baseline

Our memory read computes attention over all memory slots:

αi=softmax(q⋅kid)for i=1,…,n\alpha_i = \text{softmax}\left(\frac{\mathbf{q} \cdot \mathbf{k}_i}{\sqrt{d}}\right) \quad \text{for } i = 1, \ldots, n

This soft attention approach has an important advantage: it's fully differentiable. Gradients flow smoothly from the output through the softmax and into the query and key representations. The model can learn what to store and how to query through standard backpropagation.

But soft attention also inherits the same dilution problem we identified earlier for long-context transformers. When nn is large, each slot's weight αi\alpha_i becomes vanishingly small. The information we need gets drowned in a weighted average dominated by irrelevant entries.

Temperature: Controlling the Focus-Diffusion Tradeoff

One way to combat dilution is to sharpen the attention distribution. We can introduce a temperature parameter τ\tau that scales the scores before softmax:

αi=softmax(q⋅kid⋅τ)\alpha_i = \text{softmax}\left(\frac{\mathbf{q} \cdot \mathbf{k}_i}{\sqrt{d} \cdot \tau}\right)

The temperature τ\tau controls the "peakiness" of the distribution:

  • Low temperature (τ<1\tau < 1): Amplifies score differences. Small advantages become large, concentrating attention on the best-matching entries. In the limit τ→0\tau \to 0, this approaches hard argmax selection.

  • High temperature (τ>1\tau > 1): Dampens score differences. The distribution becomes more uniform, spreading attention across many entries.

We can measure this effect through entropy, which quantifies how spread out a distribution is:

H(α)=−∑i=1nαilog⁡αiH(\alpha) = -\sum_{i=1}^{n} \alpha_i \log \alpha_i

Low entropy means attention is concentrated on few slots; high entropy means it's spread across many.

In[10]:
Code
def soft_attention_retrieval(
    query, memory_keys, memory_values, temperature=1.0
):
    """
    Retrieve from memory using soft attention with temperature scaling.

    The temperature τ controls focus vs diffusion:
    - τ < 1: More focused (approaches hard selection)
    - τ = 1: Standard softmax
    - τ > 1: More diffuse (approaches uniform)

    Args:
        query: (batch, dim)
        memory_keys: (memory_size, dim)
        memory_values: (memory_size, value_dim)
        temperature: Scaling factor τ for attention sharpness

    Returns:
        retrieved: (batch, value_dim)
        attention: (batch, memory_size)
    """
    # Compute scaled similarities: s_i / (√d · τ)
    scores = torch.matmul(query, memory_keys.T)  # (batch, memory_size)
    scores = scores / (np.sqrt(query.shape[-1]) * temperature)

    # Soft attention weights: α_i = softmax(scores)_i
    attention = F.softmax(scores, dim=-1)

    # Weighted retrieval: r = Σ_i α_i · v_i
    retrieved = torch.matmul(attention, memory_values)

    return retrieved, attention


# Compare different temperatures
temperatures = [0.1, 0.5, 1.0, 2.0]
query = torch.randn(1, 128)
keys = torch.randn(100, 128)
values = torch.randn(100, 64)

temperature_results = []
for temp in temperatures:
    _, attn = soft_attention_retrieval(query, keys, values, temperature=temp)
    # Compute entropy: H(α) = -Σ α_i log(α_i)
    entropy = -torch.sum(attn * torch.log(attn + 1e-10), dim=-1).item()
    max_weight = attn.max().item()
    temperature_results.append(
        {"temperature": temp, "entropy": entropy, "max_weight": max_weight}
    )
Out[11]:
Console
Temperature Effect on Retrieval Sharpness:

 Temperature      Entropy   Max Weight            Interpretation
-----------------------------------------------------------------
         0.1        0.515        0.877  Very focused (near hard)
         0.5        3.313        0.179                   Focused
         1.0        4.210        0.061                  Standard
         2.0        4.500        0.027                   Diffuse
Out[12]:
Visualization
Four line plots showing attention weight distributions, from highly peaked at low temperature to more uniform at high temperature.
Attention weight distributions over 100 memory slots at different temperatures. Low temperature (τ=0.1) concentrates nearly all weight on one slot. High temperature (τ=2.0) spreads weight more uniformly, enabling information aggregation from multiple sources.

The results demonstrate the entropy-focus tradeoff concretely. At temperature 0.1, the entropy is low and the maximum weight approaches 1.0, meaning the model concentrates almost all attention on a single memory slot. The softmax has become nearly a hard argmax. At temperature 2.0, entropy increases substantially as attention spreads more evenly across slots.

This tradeoff has practical implications:

  • For fact retrieval (e.g., "What is the capital of France?"), use low temperatures. The answer exists in one specific memory location.
  • For synthesis tasks (e.g., "Summarize the key themes"), use higher temperatures. Relevant information is distributed across many entries.
  • For training, moderate temperatures (0.5-1.0) often work best. They allow gradient flow to multiple entries without completely diluting the signal.

However, temperature alone cannot solve the scalability problem. Even with sharp attention, we still compute similarities with all nn memory entries. When nn reaches millions, this becomes prohibitively expensive.

Top-k Retrieval: Trading Differentiability for Efficiency

A more radical solution abandons the soft attention over all entries. Instead, we first find the kk most relevant memory slots using a discrete selection step, then apply soft attention only over those kk entries:

Top-k(s)={i1,i2,…,ik}where si1≥si2≥⋯≥sik\text{Top-}k(\mathbf{s}) = \{i_1, i_2, \ldots, i_k\} \quad \text{where } s_{i_1} \geq s_{i_2} \geq \cdots \geq s_{i_k}

The top-kk operation returns the indices of the kk highest-scoring entries. We then compute attention only over this subset:

αj=softmax(sij)for j=1,…,k\alpha_j = \text{softmax}(s_{i_j}) \quad \text{for } j = 1, \ldots, k r=∑j=1kαj⋅vij\mathbf{r} = \sum_{j=1}^{k} \alpha_j \cdot \mathbf{v}_{i_j}

The key insight is that kk is fixed regardless of memory size nn. Whether the memory contains 1,000 or 1,000,000 entries, we only attend over kk slots in the final step. This bounds the attention computation at O(k)O(k) regardless of nn.

The Differentiability Tradeoff

There's a catch: the top-kk selection involves an argmax-like operation (finding the highest scores), which is not differentiable. During backpropagation, we cannot compute gradients through the discrete selection step.

This means entries that weren't selected receive no gradient signal, even if they were "almost" selected. The model cannot learn to make a near-miss entry more relevant. In practice, this limitation is often acceptable because:

  1. The entries that were selected still receive full gradients
  2. The key representations are typically trained with other objectives (like the main language modeling loss)
  3. The selection step can be approximated during training using straight-through estimators or Gumbel-softmax tricks

For many applications, the efficiency gains vastly outweigh this differentiability cost.

In[13]:
Code
def topk_retrieval(query, memory_keys, memory_values, k=10):
    """
    Retrieve from memory using top-k selection followed by soft attention.

    This is a two-stage process:
    1. Find the k keys with highest similarity to the query (discrete)
    2. Apply softmax attention over only those k entries (continuous)

    Complexity: O(n) for scoring + O(k) for attention = O(n) total
    but the attention step is bounded by k regardless of n
    """
    # Stage 1: Compute all n similarity scores
    scores = torch.matmul(query, memory_keys.T)  # (batch, n)
    scores = scores / np.sqrt(query.shape[-1])

    # Stage 2: Select top-k (discrete, non-differentiable)
    topk_scores, topk_indices = torch.topk(scores, k, dim=-1)

    # Gather the k corresponding values
    batch_size = query.shape[0]
    topk_values = memory_values[topk_indices.view(-1)].view(batch_size, k, -1)

    # Stage 3: Soft attention over just the k entries
    topk_attention = F.softmax(topk_scores, dim=-1)  # (batch, k)

    # Weighted sum over k entries (not n!)
    retrieved = torch.einsum("bk,bkd->bd", topk_attention, topk_values)

    return retrieved, topk_indices, topk_attention


# Demonstrate top-k retrieval on a large memory
memory_size = 10000
query = torch.randn(4, 128)
large_keys = torch.randn(memory_size, 128)
large_values = torch.randn(memory_size, 64)

for k in [5, 20, 100]:
    retrieved, indices, attn = topk_retrieval(
        query, large_keys, large_values, k=k
    )
Out[14]:
Console
Top-k Retrieval from 10,000 Memory Slots:

     k   Compute Ratio    Memory Slots Used
---------------------------------------------
     5           0.05%                    5
    20           0.20%                   20
   100           1.00%                  100

The efficiency gains are dramatic. With k=5k=5, we use only 0.05% of the memory slots for the attention computation, a 2000x reduction compared to attending to all entries. Even with k=100k=100 to retrieve more entries, we still use only 1% of the memory.

Complexity Analysis: Where the Time Goes

Let's trace through the computational costs:

  1. Scoring all keys: We still compute q⋅ki\mathbf{q} \cdot \mathbf{k}_i for all nn entries: O(n⋅d)O(n \cdot d)
  2. Finding top-k: Selecting the kk largest from nn scores: O(n)O(n) (or O(nlog⁡k)O(n \log k) for a heap-based algorithm)
  3. Attention over k: Softmax and weighted sum over kk entries: O(k⋅dv)O(k \cdot d_v)

The total is O(n⋅d)O(n \cdot d) for exact top-k retrieval. We've bounded the attention step but not the scoring step.

For truly large memories (millions of entries), even linear scoring becomes prohibitive. This is where approximate nearest neighbor (ANN) algorithms enter. Methods like FAISS, ScaNN, or Annoy build index structures that support sublinear retrieval:

  • Exact search: O(n)O(n) to find top-k
  • Approximate search: O(log⁡n)O(\log n) or even O(1)O(1) depending on the algorithm and acceptable accuracy loss

With ANN indexing, a memory of 1 million entries can be queried nearly as fast as a memory of 1,000 entries, at the cost of occasionally missing the truly best match.

Out[15]:
Visualization
Log-log plot comparing retrieval time vs memory size for exact, HNSW, and IVF search methods.
Retrieval time scaling for different algorithms. Exact search grows linearly with memory size, becoming impractical for millions of entries. Approximate methods (HNSW, IVF) achieve sub-linear scaling, making million-scale memories feasible.

Hierarchical Memory

Top-k retrieval solves the attention efficiency problem but treats all memory entries uniformly. When memories span multiple time scales, a more sophisticated organization helps: hierarchical memory separates recent detailed information from older compressed summaries.

Short-term memory holds recent context at full resolution, while long-term memory stores condensed representations from earlier. The model queries both levels, using short-term memory for detailed recent information and long-term memory for general patterns.

Out[16]:
Visualization
Diagram showing two rectangular memory banks stacked vertically, with arrows from a query box pointing to both levels.
Hierarchical memory organization with two levels. Short-term memory (top) holds detailed recent information with high-resolution keys. Long-term memory (bottom) stores compressed summaries from earlier context. Queries attend to both levels with different weightings.

The hierarchical approach mirrors how human memory works: detailed episodic memory for recent events, condensed semantic memory for older knowledge. For language models, this translates to storing recent token representations at full resolution while compressing older context into summary vectors.