Recurrent Memory › Transformer-XL: Segment-Level Recurrence
Language AI HandbookAll chapters 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 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 of segment , the model:
- Retrieves the cached hidden states from processing the previous segment at layer
- Concatenates these cached states with the current segment's hidden states
- Computes attention where queries come only from the current segment, but keys and values span both the cached and current states
- 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 , not layer . When processing layer , we concatenate the previous segment's layer output with the current segment's layer output and compute attention. This is not an implementation detail but a consequence of how information flows through a multi-layer network. At layer , 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 , which contains tokens. The previous segment's hidden states, also of length (or some memory length ), have been cached. At layer , we want to compute attention over an extended context that includes both segments.
The extended context for attention at layer concatenates the stop-gradient of the previous segment's hidden states with the current segment's hidden states. Concretely:
where:
- : the extended hidden states combining previous and current segments, used as keys and values
- : cached hidden states from the previous segment at layer
- : current segment's hidden states at layer
- : stops gradient flow to prevent backpropagating through the cached states
- : concatenation along the sequence dimension, resulting in a tensor of length
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:
where:
- : queries from the current segment only
- : keys and values from the extended context (current + cached)
- : learnable projection matrices for layer
- : the length of the cached memory (typically equal to segment length )
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 (only current tokens ask questions) while the key and value dimensions are (both cached and current tokens can be referenced). The attention matrix has shape , 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.
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# 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
)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.

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.