Sitemap
noplaxochia

noplaxochia

ML/GenAI/Autonomous Driving Contact me through Gmail: wangdk93.

Multihead attention from scratch

4 min readJul 9, 2024

--

multihead_attn = nn.MultiheadAttention(embed_dim, num_heads)
attn_output, attn_output_weights = multihead_attn(query, key, value)

Pytorch without using nn.MultiheadAttention.

Components:

  • Linear layers for qkv.
  • scaled_dot_product with softmax.
  • Use num_head to split the embedded dimension.
  • Final linear layer for redistributing combined head.

Get noplaxochia’s stories in your inbox

Join Medium for free to get updates from this writer.

Cross attention Version A

import torch
import torch.nn as nn
import torch.nn.functional as F
import math

class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super(MultiHeadAttention, self).__init__()
assert d_model % num_heads == 0, "d_model must be divisible by num_heads"

self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads

self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)

def scaled_dot_product_attention(self, Q, K, V, mask=None):
attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)

if mask is not None:
attn_scores = attn_scores.masked_fill(mask == 0, -1e9)

attn_probs = F.softmax(attn_scores, dim=-1)
output = torch.matmul(attn_probs, V)
return output

def split_heads(self, x):
batch_size, seq_length, d_model = x.size()
return x.view(batch_size, seq_length, self.num_heads, self.d_k).transpose(1, 2)

def combine_heads(self, x):
batch_size, num_heads, seq_length, d_k = x.size()
return x.transpose(1, 2).contiguous().view(batch_size, seq_length, self.d_model)

def forward(self, Q, K, V, mask=None):
Q = self.split_heads(self.W_q(Q))
K = self.split_heads(self.W_k(K))
V = self.split_heads(self.W_v(V))

attn_output = self.scaled_dot_product_attention(Q, K, V, mask)
output = self.W_o(self.combine_heads(attn_output))
return output

# Example usage
d_model = 8
num_heads = 4
query_seq_length = 5
ke_seq_length = 6
batch_size = 1

mha = MultiHeadAttention(d_model, num_heads)

# Create dummy input tensors
Q = torch.randn(batch_size, query_seq_length, d_model)
K = V = torch.randn(batch_size, ke_seq_length, d_model)

# Create a dummy mask (optional)
mask = torch.ones(batch_size, 1, query_seq_length, ke_seq_length)

# Forward pass
output = mha(Q, K, V, mask)

Self attention.

import torch
import torch.nn as nn
import torch.nn.functional as F

class MultiheadAttention(nn.Module):
def __init__(self, embed_dim, num_heads):
super(MultiheadAttention, self).__init__()
assert embed_dim % num_heads == 0, "Embedding dimension must be divisible by number of heads"

self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads

# Linear layers for Q, K, V
self.linear_q = nn.Linear(embed_dim, embed_dim)
self.linear_k = nn.Linear(embed_dim, embed_dim)
self.linear_v = nn.Linear(embed_dim, embed_dim)

# Final linear layer
self.final_linear = nn.Linear(embed_dim, embed_dim)

# Dropout layer
self.dropout = nn.Dropout(p=0.1)

def scaled_dot_product_attention(self, query, key, value):
# Calculate the dot product
scores = torch.matmul(query, key.transpose(-2, -1)) / (self.head_dim ** 0.5)

# Apply softmax to get attention scores
attn = F.softmax(scores, dim=-1)

# Apply dropout
attn = self.dropout(attn)

# Multiply by value
output = torch.matmul(attn, value)
return output

def forward(self, query, key, value):
batch_size = query.size(0)

# Perform linear operation and split into h heads

query_l = self.linear_q(query)
query = query_l.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
key = self.linear_k(key).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
value = self.linear_v(value).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)

# Apply scaled dot product attention
attn_output = self.scaled_dot_product_attention(query, key, value)

# Concatenate heads and put through final linear layer
attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, -1, self.embed_dim)
# combine and redistribute with a final layer
output = self.final_linear(attn_output)

return output

# Assuming the MultiheadAttention class is already defined as shown in the previous example

# Set the random seed for reproducibility
torch.manual_seed(0)

# Define the dimensions
embed_dim = 8 # Embedding size for each token
num_heads = 4 # Number of attention heads
seq_length = 5 # Length of the sequence

# Create an instance of MultiheadAttention
multihead_attn = MultiheadAttention(embed_dim, num_heads)

# Simulate a batch of token embeddings (batch size = 1)
token_embeddings = torch.randn(1, seq_length, embed_dim)

# Apply the MultiheadAttention layer (self-attention)
output = multihead_attn(token_embeddings, token_embeddings, token_embeddings)

print("Input Embeddings:")
print(token_embeddings)
print("\nOutput of Multihead Attention:")
print(output)

With mask

import torch
import torch.nn as nn
import torch.nn.functional as F

class MultiheadAttention(nn.Module):
def __init__(self, embed_dim, num_heads):
super(MultiheadAttention, self).__init__()
assert embed_dim % num_heads == 0, "Embedding dimension must be divisible by number of heads"

self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads

self.linear_q = nn.Linear(embed_dim, embed_dim)
self.linear_k = nn.Linear(embed_dim, embed_dim)
self.linear_v = nn.Linear(embed_dim, embed_dim)

self.final_linear = nn.Linear(embed_dim, embed_dim)
self.dropout = nn.Dropout(p=0.1)

def scaled_dot_product_attention(self, query, key, value, attn_mask=None, key_padding_mask=None):
# Scale dot product
scores = torch.matmul(query, key.transpose(-2, -1)) / (self.head_dim ** 0.5)

if attn_mask is not None:
scores = scores.masked_fill(attn_mask == 0, float('-inf'))

if key_padding_mask is not None:
# Assuming key_padding_mask is a ByteTensor with shape [batch_size, seq_len] where padding elements are True
scores = scores.masked_fill(key_padding_mask.unsqueeze(1).unsqueeze(2), float('-inf'))

attn = F.softmax(scores, dim=-1)
attn = self.dropout(attn)
output = torch.matmul(attn, value)
return output

def forward(self, query, key, value, attn_mask=None, key_padding_mask=None):
batch_size = query.size(0)

query = self.linear_q(query).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
key = self.linear_k(key).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
value = self.linear_v(value).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)

attn_output = self.scaled_dot_product_attention(query, key, value, attn_mask, key_padding_mask)

attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, -1, self.embed_dim)
output = self.final_linear(attn_output)

return output

# Assuming the MultiheadAttention class is defined as in the previous example

# Set random seed for reproducibility
torch.manual_seed(0)

# Define dimensions
embed_dim = 8 # Embedding size for each token
num_heads = 2 # Number of attention heads
batch_size = 2
seq_length = 4 # Length of the sequence

# Create an instance of MultiheadAttention
multihead_attn = MultiheadAttention(embed_dim, num_heads)

# Simulate a batch of token embeddings
# Batch size is 2, sequence length is 4, embedding dimension is 8
token_embeddings = torch.randn(batch_size, seq_length, embed_dim)

# Create key padding mask
# Assume the second sequence in the batch has two padding tokens at the end
key_padding_mask = torch.tensor([
[0, 0, 0, 0], # No padding
[0, 0, 1, 1] # Last two are padding
], dtype=torch.bool)

# Create attention mask
# Prevent the first token from attending to the third token in each sequence
attn_mask = torch.zeros((batch_size, seq_length, seq_length), dtype=torch.bool)
attn_mask[:, 0, 2] = 1

# Apply the MultiheadAttention layer
output = multihead_attn(token_embeddings, token_embeddings, token_embeddings,
attn_mask=attn_mask, key_padding_mask=key_padding_mask)

print("Input Embeddings:")
print(token_embeddings)
print("\nOutput of Multihead Attention:")
print(output)

--

--

noplaxochia
noplaxochia

ML/GenAI/Autonomous Driving Contact me through Gmail: wangdk93.