Skip to content
AILinkDeepTech
Go back
Deep Learning Medium

FlashAttention Implementation in PyTorch

Abstract

A PyTorch implementation of FlashAttention-style multi-head attention: a softmax-scaled dot-product attention block, a tiled/blocked variant that processes sequence chunks for memory efficiency, and basic shape tests.

FlashAttention Implementation in PyTorch

This implementation builds FlashAttention-style multi-head attention from scratch. It includes a standard scaled dot-product attention module, a tiled/blocked variant that processes the sequence in chunks to reduce peak memory, and a test function covering shapes and variable sequence lengths.

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

class FlashAttention(nn.Module):
    def __init__(self, dim, num_heads=8, dropout=0.1, max_seq_length=1024):
        super().__init__()
        self.num_heads = num_heads
        self.head_dim = dim // num_heads
        self.scaling = self.head_dim ** -0.5
        
        self.max_seq_length = max_seq_length
        self.dropout = dropout

        # Linear projections
        self.q_proj = nn.Linear(dim, dim)
        self.k_proj = nn.Linear(dim, dim)
        self.v_proj = nn.Linear(dim, dim)
        self.out_proj = nn.Linear(dim, dim)

    def forward(self, x, mask=None):
        batch_size, seq_length, dim = x.shape

        # Project and reshape queries, keys, and values
        q = self.q_proj(x).reshape(batch_size, seq_length, self.num_heads, self.head_dim)
        k = self.k_proj(x).reshape(batch_size, seq_length, self.num_heads, self.head_dim)
        v = self.v_proj(x).reshape(batch_size, seq_length, self.num_heads, self.head_dim)
        
        q = q.transpose(1, 2)  # (batch_size, num_heads, seq_length, head_dim)
        k = k.transpose(1, 2)
        v = v.transpose(1, 2)

        # Scaled dot-product attention 
        attn_weights = torch.matmul(q, k.transpose(-2, -1)) * self.scaling
        
        if mask is not None:
            attn_weights = attn_weights.masked_fill(mask == 0, float('-inf'))
        
        attn_weights = F.softmax(attn_weights, dim=-1)
        attn_weights = F.dropout(attn_weights, p=self.dropout, training=self.training)

        attn_output = torch.matmul(attn_weights, v)
        
        attn_output = attn_output.transpose(1, 2).reshape(batch_size, seq_length, dim)
        attn_output = self.out_proj(attn_output)

        return attn_output
    
class FlashAttentionWithTiling(FlashAttention):
    """
    tiling for better memory efficiency.
    This splits the input sequence into blocks and processes them in chunks.
    """
    def __init__(self, dim, num_heads=8, dropout=0.1, max_seq_length=1024, block_size=64):
        super().__init__(dim, num_heads, dropout, max_seq_length)
        self.block_size = block_size

    def forward(self, x, mask=None):
        batch_size, seq_length, dim = x.shape

        q = self.q_proj(x).reshape(batch_size, seq_length, self.num_heads, self.head_dim)
        k = self.k_proj(x).reshape(batch_size, seq_length, self.num_heads, self.head_dim)
        v = self.v_proj(x).reshape(batch_size, seq_length, self.num_heads, self.head_dim)
        
        q = q.transpose(1, 2)
        k = k.transpose(1, 2)
        v = v.transpose(1, 2)

        output = torch.zeros_like(q)

        # Process in blocks
        for i in range(0, seq_length, self.block_size):
            q_block = q[:, :, i:min(i + self.block_size, seq_length), :]

            # Initialize block attention
            block_weights = torch.zeros(batch_size, self.num_heads, 
                                     q_block.size(2), seq_length, 
                                     device=q.device)
            
            for j in range(0, seq_length, self.block_size):
                k_block = k[:, :, j:min(j + self.block_size, seq_length), :]
                v_block = v[:, :, j:min(j + self.block_size, seq_length), :]
                
                scores = torch.matmul(q_block, k_block.transpose(-2, -1)) * self.scaling
                
                if mask is not None:
                    block_mask = mask[:, :, i:i + self.block_size, j:j + self.block_size]
                    scores = scores.masked_fill(block_mask == 0, float('-inf'))
                
                block_weights[:, :, :, j:j + k_block.size(2)] = scores

            block_weights = F.softmax(block_weights, dim=-1)
            block_weights = F.dropout(block_weights, p=self.dropout, training=self.training)
            
            block_output = torch.zeros_like(q_block)

            # Compute block output by processing each key/value block
            for j in range(0, seq_length, self.block_size):
                v_block = v[:, :, j:min(j + self.block_size, seq_length), :]
                block_weights_slice = block_weights[:, :, :, j:j + v_block.size(2)]
                block_output += torch.matmul(block_weights_slice, v_block)

            output[:, :, i:i + q_block.size(2), :] = block_output

        output = output.transpose(1, 2).reshape(batch_size, seq_length, dim)
        output = self.out_proj(output)

        return output
    
def test_flash_attention():
    batch_size = 2
    seq_length = 128
    dim = 256
    num_heads = 8

    flash_attn = FlashAttention(dim=dim, num_heads=num_heads)
    flash_attn_tiling = FlashAttentionWithTiling(dim=dim, num_heads=num_heads, block_size=32)
    
    x = torch.randn(batch_size, seq_length, dim)
    
    mask = torch.ones(batch_size, 1, seq_length, seq_length)
    
    output1 = flash_attn(x, mask)
    
    output2 = flash_attn_tiling(x, mask)
    
    assert output1.shape == (batch_size, seq_length, dim)
    assert output2.shape == (batch_size, seq_length, dim)

    # Verify that outputs are different
    assert not torch.allclose(output1, output2, atol=1e-4)
    
    print("Basic shape tests passed!")

    # Test with different sequence lengths
    x_short = torch.randn(batch_size, seq_length // 2, dim)
    mask_short = torch.ones(batch_size, 1, seq_length // 2, seq_length // 2)
    
    output_short1 = flash_attn(x_short, mask_short)
    output_short2 = flash_attn_tiling(x_short, mask_short)
    
    assert output_short1.shape == (batch_size, seq_length // 2, dim)
    assert output_short2.shape == (batch_size, seq_length // 2, dim)
    
    print("Variable sequence length tests passed!")

    return "All tests passed successfully!"

if __name__ == "__main__":
    test_flash_attention()


Cite this Explanation

@article{ailinkdeeptech2025flashattentionalgo,
  title={FlashAttention Implementation in PyTorch},
  author={AILinkDeepTech},
  journal={AILinkDeepTech Algorithm Explanations},
  year={2025},
  url={https://ailinkdeeptech.com/research/flashattention_algo}
}

Related Explanations