Recurrent Memory › Transformer-XL: Segment-Level Recurrence

Michael BrenndoerferUpdated July 6, 20259 min read
Language AI HandbookAll chapters
Part 18 of 67Long Context
Chapter 132Recurrent Memory › Transformer-XL: Segment-Level Recurrence
Reading section 3 of 14 · Transformer-XL: Segment-Level Recurrence · 9 minSection 3 of 14 · 9 min

Book contents

Section 3 of 14 · 9 min read

Recurrent Memory › Transformer-XL: Segment-Level RecurrenceOne section · Sections in this chapter3 / 14 ↓
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

Now that we understand the problem, let's explore how Transformer-XL solves it. The solution is remarkably simple: instead of discarding the previous segment entirely, cache its hidden states and make them available during attention computation for the current segment.

Think of it like this: when you read a new paragraph, you don't forget the previous one. You carry forward a mental summary of what came before. That summary isn't the original words themselves, but your processed understanding of them. Transformer-XL does exactly this, but with hidden states instead of mental summaries. The cached states are not raw token embeddings. They are processed representations that already encode relationships between tokens, contextual information from within that segment, and whatever higher-level abstractions the network has learned to extract.

The key insight is that we do not need to preserve the original tokens from previous segments. We need to preserve the information they contained in a form that is useful for processing the current segment. Hidden states serve this purpose because they encode meaning rather than surface form alone. A token's hidden state at layer 3 is less like "the word 'cat'" and more like "a specific cat being discussed in a context involving a mat and some sitting." That richer representation is what gets cached and carried forward.

This approach also fits naturally with the autoregressive nature of language modeling. When generating text one token at a time, we process segments sequentially anyway. Caching the hidden states of the just-processed segment and feeding them to the next segment adds minimal overhead to the already-sequential processing. There is no need to maintain an exponentially growing memory buffer; we only ever need the most recent segment's hidden states.

Segment-Level Recurrence

Segment-level recurrence is a technique where hidden states from the previous segment are cached and concatenated with the current segment's keys and values during attention computation. This allows information to flow across segment boundaries without recomputing attention over the entire history.

The Mechanism Step by Step

The recurrence operates at each layer independently. When processing layer nn of segment τ\tau, the model:

  1. Retrieves the cached hidden states hτ−1n−1\mathbf{h}_{\tau-1}^{n-1} from processing the previous segment at layer n−1n-1
  2. Concatenates these cached states with the current segment's hidden states
  3. Computes attention where queries come only from the current segment, but keys and values span both the cached and current states
  4. Caches the current segment's output hidden states for use when processing the next segment

This creates an asymmetric attention pattern: current tokens can "look back" at cached tokens, but we never recompute outputs for the cached tokens. They're frozen representations from the previous forward pass. This asymmetry is not a limitation but a design choice. Recomputing the outputs of cached tokens would require backpropagating gradients through an unbounded history, which is exactly the computational cost we are trying to avoid. By treating cached states as read-only context, Transformer-XL achieves information flow across segments at constant computational cost per segment.

Another subtle but important point is that the cached states are from layer n−1n-1, not layer nn. When processing layer nn, we concatenate the previous segment's layer n−1n-1 output with the current segment's layer n−1n-1 output and compute attention. This is not an implementation detail but a consequence of how information flows through a multi-layer network. At layer nn, the current segment's representation already incorporates information from lower layers. The cached representation we need to extend must be at the same "processing depth" to combine coherently with the current representation.

The Mathematical Formulation

Let's formalize this mechanism. Suppose we're processing segment τ\tau, which contains LL tokens. The previous segment's hidden states, also of length LL (or some memory length MM), have been cached. At layer nn, we want to compute attention over an extended context that includes both segments.

The extended context for attention at layer nn concatenates the stop-gradient of the previous segment's hidden states with the current segment's hidden states. Concretely:

h~τn−1=StopGrad(hτ−1n−1)∘hτn−1\tilde{\mathbf{h}}_\tau^{n-1} = \text{StopGrad}(\mathbf{h}_{\tau-1}^{n-1}) \circ \mathbf{h}_\tau^{n-1}

where:

  • h~τn−1\tilde{\mathbf{h}}_\tau^{n-1}: the extended hidden states combining previous and current segments, used as keys and values
  • hτ−1n−1\mathbf{h}_{\tau-1}^{n-1}: cached hidden states from the previous segment at layer n−1n-1
  • hτn−1\mathbf{h}_\tau^{n-1}: current segment's hidden states at layer n−1n-1
  • StopGrad(⋅)\text{StopGrad}(\cdot): stops gradient flow to prevent backpropagating through the cached states
  • ∘\circ: concatenation along the sequence dimension, resulting in a tensor of length M+LM + L

Why does StopGrad matter? If we allowed gradients to flow back through the cached states, we would need to store the computation graph for the previous segment's forward pass, then the segment before that, and so on, going back all the way to the beginning of the document. This would make the memory cost proportional to the total document length, defeating the purpose of segmentation. By stopping gradients at the cached states, each segment's backward pass touches only the current segment's computation, keeping memory usage constant regardless of document length.

The attention computation then becomes:

qτn=hτn−1Wqn,kτn=h~τn−1Wkn,vτn=h~τn−1Wvn\mathbf{q}_\tau^n = \mathbf{h}_\tau^{n-1} W_q^n, \quad \mathbf{k}_\tau^n = \tilde{\mathbf{h}}_\tau^{n-1} W_k^n, \quad \mathbf{v}_\tau^n = \tilde{\mathbf{h}}_\tau^{n-1} W_v^n hτn=Attention(qτn,kτn,vτn)\mathbf{h}_\tau^n = \text{Attention}(\mathbf{q}_\tau^n, \mathbf{k}_\tau^n, \mathbf{v}_\tau^n)

where:

  • qτn∈RL×d\mathbf{q}_\tau^n \in \mathbb{R}^{L \times d}: queries from the current segment only
  • kτn,vτn∈R(L+M)×d\mathbf{k}_\tau^n, \mathbf{v}_\tau^n \in \mathbb{R}^{(L + M) \times d}: keys and values from the extended context (current + cached)
  • Wqn,Wkn,WvnW_q^n, W_k^n, W_v^n: learnable projection matrices for layer nn
  • MM: the length of the cached memory (typically equal to segment length LL)

The critical detail is that queries come only from the current segment while keys and values include the cached previous segment. This asymmetry is intentional: we want current tokens to attend to past context, but we don't want to regenerate outputs for past tokens. The cached states are read-only: they provide context but don't receive updates.

Why does this formula make sense? Notice that the query dimension remains LL (only current tokens ask questions) while the key and value dimensions are L+ML + M (both cached and current tokens can be referenced). The attention matrix has shape (L,L+M)(L, L+M), which means each current token computes a score against every cached token and every preceding current token. The resulting weighted sum draws from both the cached and current portions of the extended context, exactly what we want.

Implementing the Mechanism

Let's translate this into code. The core operation is straightforward: concatenate cached and current hidden states, project to queries/keys/values, and compute attention with appropriate masking.

In[6]:
Code
import numpy as np


def transformer_xl_attention(
    current_hidden: np.ndarray,
    cached_hidden: np.ndarray,
    W_q: np.ndarray,
    W_k: np.ndarray,
    W_v: np.ndarray,
    d_k: int,
) -> tuple[np.ndarray, np.ndarray]:
    """
    Compute Transformer-XL style attention with segment-level recurrence.

    Args:
        current_hidden: Current segment hidden states, shape (L, d)
        cached_hidden: Cached previous segment states, shape (M, d)
        W_q, W_k, W_v: Projection matrices, shape (d, d)
        d_k: Key dimension for scaling

    Returns:
        attention_output: Output for current segment, shape (L, d)
        attention_weights: Attention pattern, shape (L, L+M)
    """
    # Concatenate for extended context
    extended_hidden = np.concatenate([cached_hidden, current_hidden], axis=0)

    # Project to queries, keys, values
    # Queries: only from current segment
    queries = current_hidden @ W_q  # (L, d)
    # Keys and values: from extended context
    keys = extended_hidden @ W_k  # (L+M, d)
    values = extended_hidden @ W_v  # (L+M, d)

    # Compute attention scores
    scores = queries @ keys.T / np.sqrt(d_k)  # (L, L+M)

    # Apply causal masking
    # Current segment tokens can attend to all of cache + their own past
    L = current_hidden.shape[0]
    M = cached_hidden.shape[0]
    mask = np.ones_like(scores) * float("-inf")
    for i in range(L):
        # Can attend to all cached tokens + tokens up to and including position i
        mask[i, : M + i + 1] = 0
    scores = scores + mask

    # Softmax
    exp_scores = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
    attention_weights = exp_scores / exp_scores.sum(axis=-1, keepdims=True)

    # Weighted sum of values
    output = attention_weights @ values  # (L, d)

    return output, attention_weights
In[7]:
Code
# Demonstrate the mechanism with a simple example
rng = np.random.default_rng(42)

segment_length = 8
memory_length = 8
hidden_dim = 16
d_k = hidden_dim

# Simulate hidden states
current_segment = rng.standard_normal((segment_length, hidden_dim)) * 0.1
cached_segment = rng.standard_normal((memory_length, hidden_dim)) * 0.1

# Random projection matrices (in practice, these are learned)
W_q = rng.standard_normal((hidden_dim, hidden_dim)) * 0.1
W_k = rng.standard_normal((hidden_dim, hidden_dim)) * 0.1
W_v = rng.standard_normal((hidden_dim, hidden_dim)) * 0.1

output, attention_weights = transformer_xl_attention(
    current_segment, cached_segment, W_q, W_k, W_v, d_k
)
Out[8]:
Console
Current segment shape: (8, 16)
Cached segment shape: (8, 16)
Attention weights shape: (8, 16)
Output shape: (8, 16)

Attention weight distribution for token 0 of current segment:
  Attention to cached tokens (0-7): 0.889
  Attention to current token (8): 0.111

The output shows that token 0 of the current segment allocates substantial attention to the cached memory. This is expected: with no preceding tokens in the current segment, the memory provides all available context. The attention weights reveal how information flows. The first token in the current segment can attend to all 8 cached tokens plus itself. Later tokens in the current segment can attend to even more context: all cached tokens plus all preceding tokens in the current segment.

Out[9]:
Visualization
Heatmap showing attention weights with a vertical segment boundary, where current tokens attend to both cached and current positions.
Transformer-XL attention pattern showing segment-level recurrence. Current segment tokens (positions 8-15) can attend to cached previous segment tokens (positions 0-7) as well as their own causal context. The triangular pattern in the right half reflects causal self-attention, while the rectangular left half shows uniform access to cached memory.

The attention pattern shows the distinctive Transformer-XL signature: a triangular pattern in the current segment (causal self-attention) combined with a rectangular region on the left (attention to cached memory). Every token in the current segment can see the entire cached memory, and each token can additionally see all the preceding tokens in the current segment.