ALiBi › ALiBi in Attention: Complete Implementation

Michael BrenndoerferUpdated May 30, 20256 min read
Language AI HandbookAll chapters
Part 14 of 67Positional Encoding
Chapter 101ALiBi › ALiBi in Attention: Complete Implementation
Reading section 7 of 16 · ALiBi in Attention: Complete Implementation · 6 minSection 7 of 16 · 6 min

Book contents

Section 7 of 16 · 6 min read

ALiBi › ALiBi in Attention: Complete ImplementationOne section · Sections in this chapter7 / 16 ↓
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 we have all the pieces to implement ALiBi-augmented attention from scratch. The implementation closely mirrors standard attention. We compute and scale the score matrix, add the bias, apply the optional causal mask, take the softmax, and use the result to weight the values.

The only new element is the construction and addition of the ALiBi bias tensor. For multi-head attention, we need one bias matrix per head, which we stack into a three-dimensional tensor of shape (num_heads, seq_len, seq_len). Each slice along the first dimension is the bias matrix for one head, with that head's specific slope.

In[13]:
Code
def softmax(x, axis=-1):
    """Numerically stable softmax."""
    x_max = np.max(x, axis=axis, keepdims=True)
    exp_x = np.exp(x - x_max)
    return exp_x / np.sum(exp_x, axis=axis, keepdims=True)


def alibi_attention(Q, K, V, slopes, causal=True):
    """
    Compute attention with ALiBi position encoding.

    Args:
        Q: Query matrix of shape (num_heads, seq_len, d_k)
        K: Key matrix of shape (num_heads, seq_len, d_k)
        V: Value matrix of shape (num_heads, seq_len, d_v)
        slopes: ALiBi slopes, one per head, shape (num_heads,)
        causal: Whether to apply causal masking

    Returns:
        Output of shape (num_heads, seq_len, d_v)
        Attention weights of shape (num_heads, seq_len, seq_len)
    """
    num_heads, seq_len, d_k = Q.shape

    # Compute raw attention scores: Q @ K^T
    # Shape: (num_heads, seq_len, seq_len)
    scores = np.matmul(Q, K.transpose(0, 2, 1))

    # Scale by sqrt(d_k)
    scores = scores / np.sqrt(d_k)

    # Create ALiBi biases for each head
    # Shape: (num_heads, seq_len, seq_len)
    positions = np.arange(seq_len)
    distances = np.abs(positions[:, None] - positions[None, :])

    # Broadcast slopes: (num_heads, 1, 1) * (seq_len, seq_len)
    alibi_bias = -slopes[:, None, None] * distances[None, :, :]

    # Apply causal mask if needed
    if causal:
        causal_mask = np.triu(np.ones((seq_len, seq_len)), k=1)
        alibi_bias = np.where(causal_mask == 1, -np.inf, alibi_bias)

    # Add ALiBi bias to scores
    scores = scores + alibi_bias

    # Apply softmax to get attention weights
    attention_weights = softmax(scores, axis=-1)

    # Handle NaN from -inf (all masked positions)
    attention_weights = np.nan_to_num(attention_weights, nan=0.0)

    # Compute output: weighted sum of values
    output = np.matmul(attention_weights, V)

    return output, attention_weights

The broadcasting step deserves attention. We have slopes with shape (num_heads,). Reshaping to (num_heads, 1, 1) and multiplying by distances[None, :, :] with shape (1, seq_len, seq_len) produces a tensor of shape (num_heads, seq_len, seq_len). Each head's slice is the distance matrix multiplied by that head's slope. This is a single vectorized operation that replaces what would otherwise be a loop over heads.

Let's test this implementation with a concrete example to verify the shapes and inspect the attention patterns:

In[14]:
Code
# Create a simple example
np.random.seed(42)

num_heads = 4
seq_len = 8
d_k = 16
d_v = 16

# Random Q, K, V matrices
Q = np.random.randn(num_heads, seq_len, d_k) * 0.5
K = np.random.randn(num_heads, seq_len, d_k) * 0.5
V = np.random.randn(num_heads, seq_len, d_v) * 0.5

# Get ALiBi slopes
slopes = get_alibi_slopes(num_heads)

# Compute ALiBi attention
output, attention_weights = alibi_attention(Q, K, V, slopes, causal=True)
Out[15]:
Console
Output shape: (4, 8, 16)
Attention weights shape: (4, 8, 8)

Attention weights for head 1 (steep slope = 0.250):
[[1.    0.    0.    0.    0.    0.    0.    0.   ]
 [0.45  0.55  0.    0.    0.    0.    0.    0.   ]
 [0.233 0.37  0.397 0.    0.    0.    0.    0.   ]
 [0.227 0.205 0.262 0.306 0.    0.    0.    0.   ]
 [0.118 0.068 0.121 0.279 0.414 0.    0.    0.   ]
 [0.083 0.086 0.13  0.201 0.176 0.324 0.    0.   ]
 [0.065 0.089 0.092 0.127 0.137 0.272 0.218 0.   ]
 [0.025 0.038 0.057 0.073 0.136 0.214 0.233 0.224]]

Attention weights for head 4 (gentle slope = 0.003906):
[[1.    0.    0.    0.    0.    0.    0.    0.   ]
 [0.562 0.438 0.    0.    0.    0.    0.    0.   ]
 [0.36  0.453 0.187 0.    0.    0.    0.    0.   ]
 [0.344 0.23  0.245 0.181 0.    0.    0.    0.   ]
 [0.184 0.232 0.181 0.169 0.233 0.    0.    0.   ]
 [0.121 0.125 0.286 0.214 0.096 0.158 0.    0.   ]
 [0.104 0.124 0.171 0.176 0.08  0.175 0.169 0.   ]
 [0.109 0.137 0.063 0.124 0.158 0.147 0.163 0.099]]

Compare the attention patterns between heads. Head 1 with its steep slope concentrates attention heavily on recent positions, with weights dropping rapidly as distance increases. The rows show a clear diagonal structure: most of the weight is on the most recent 1 to 3 tokens, with essentially nothing beyond 5 positions. Head 4 with its gentle slope distributes attention more evenly across the available context. The rows are more uniform, with distant positions receiving non-negligible weight.

To understand the impact of ALiBi more directly, let's compare attention patterns with and without the position bias. We'll compute attention for the same queries and keys, once with standard attention (no position encoding) and once with ALiBi:

Out[16]:
Visualization
Heatmap showing attention weights without position encoding, with relatively uniform distribution.
Standard attention without position encoding. Attention weights depend only on content similarity between queries and keys. The pattern is irregular and non-local, with no systematic preference for nearby tokens.
Heatmap showing attention weights with ALiBi, showing stronger diagonal pattern due to locality bias.
ALiBi attention with the same queries and keys. The position bias systematically pulls weight toward recent tokens, creating a diagonal structure. Content signals can still override the bias for strongly relevant distant tokens.

The difference is striking. Standard attention distributes weight based purely on content similarity, sometimes attending strongly to distant positions because the content happens to match well. ALiBi reshapes this pattern, pulling attention toward recent tokens while still allowing content to influence the final distribution. This locality bias emerges from a single matrix addition, requiring no learned position embeddings and no modifications to the token representations themselves.

Out[17]:
Visualization
Heatmap of attention weights for head 1 showing strong diagonal pattern with rapid falloff.
Attention weights for head 1 (steep slope 0.5). Each row shows how one query position distributes attention across key positions. The strong diagonal pattern confirms that attention concentrates sharply on nearby tokens, with distant positions receiving negligible weight despite their content.
Out[18]:
Visualization
Heatmap of attention weights for head 4 showing broader attention distribution.
Attention weights for head 4 (slope 0.0625). Attention spreads more broadly across all available positions, with no strong locality bias. This head can pick up on long-range semantic relationships that the steep-slope heads miss.