Skip to content
AILinkDeepTech
Go back
Computer Vision Medium

Vision Transformer (ViT) Implementation in PyTorch

Abstract

A PyTorch implementation of the Vision Transformer (ViT) with patch embedding, learnable class token and position embeddings, multi-head self-attention, MLP blocks, and classification head.

Vision Transformer (ViT) Implementation in PyTorch

This implementation builds a Vision Transformer from scratch. It includes patch embedding, a learnable class token and position embeddings, multi-head self-attention with fused QKV projection, an MLP feed-forward block, and a classification head.

import torch
import torch.nn as nn
import torchvision.transforms as transforms
from einops import rearrange
from einops.layers.torch import Rearrange

class PatchEmbedding(nn.Module):
    def __init__(self, image_size=224, patch_size=16, in_channels=3, embed_dim=768):
        super().__init__()
        self.image_size = image_size
        self.patch_size = patch_size
        self.num_patches = (image_size // patch_size) ** 2

        # image into patches and flatten
        self.projection = nn.Sequential(
            # (batch, channel, height, width) -> (batch, num_patches, patch_dim)
            Rearrange('b c (h p1) (w p2) -> b (h w) (p1 p2 c)', 
                      p1=patch_size, p2=patch_size),
            nn.Linear(patch_size * patch_size * in_channels, embed_dim)
        )

    def forward(self, x):
        return self.projection(x)
    
class MultiHeadSelfAttention(nn.Module):
    def __init__(self, embed_dim, num_heads=8, dropout=0.0):
        super().__init__()
        self.num_heads = num_heads
        self.head_dim = embed_dim // num_heads
        self.scale = self.head_dim ** -0.5  

        self.qkv = nn.Linear(embed_dim, embed_dim * 3)
        self.attn_dropout = nn.Dropout(dropout)
        self.output_projection = nn.Linear(embed_dim, embed_dim)
        self.output_dropout = nn.Dropout(dropout)

    def forward(self, x):
        batch_size, num_tokens, embed_dim = x.shape
        
        qkv = self.qkv(x)
        qkv = qkv.reshape(batch_size, num_tokens, 3, self.num_heads, self.head_dim)
        qkv = qkv.permute(2, 0, 3, 1, 4)  # (3, batch, heads, tokens, head_dim)
        q, k, v = qkv[0], qkv[1], qkv[2]

        # scaled dot-product attention
        attn_scores = (q @ k.transpose(-2, -1)) * self.scale
        attn_probs = attn_scores.softmax(dim=-1)
        attn_probs = self.attn_dropout(attn_probs)

        # output
        x = (attn_probs @ v).transpose(1, 2).reshape(batch_size, num_tokens, embed_dim)
        x = self.output_projection(x)
        x = self.output_dropout(x)
        return x
    
class FeedForward(nn.Module):
    def __init__(self, embed_dim, hidden_dim, dropout=0.0):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(embed_dim, hidden_dim),
            nn.GELU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim, embed_dim),
            nn.Dropout(dropout)
        )

    def forward(self, x):
        return self.net(x)
    
class TransformerBlock(nn.Module):
    def __init__(self, embed_dim, num_heads, mlp_ratio=4.0, dropout=0.0):
        super().__init__()
        self.norm1 = nn.LayerNorm(embed_dim)
        self.attention = MultiHeadSelfAttention(embed_dim, num_heads, dropout)
        self.norm2 = nn.LayerNorm(embed_dim)
        self.feedforward = FeedForward(
            embed_dim,
            int(embed_dim * mlp_ratio),
            dropout
        )

    def forward(self, x):
        x = x + self.attention(self.norm1(x))
        x = x + self.feedforward(self.norm2(x))
        return x
    
class VisionTransformer(nn.Module):
    def __init__(
        self, 
        image_size=224,
        patch_size=16,
        in_channels=3,
        num_classes=1000,
        embed_dim=768,
        depth=12,
        num_heads=12,
        mlp_ratio=4.0,
        dropout=0.0
    ):
        super().__init__()

        self.patch_embed = PatchEmbedding(
            image_size, patch_size, in_channels, embed_dim
        )
        num_patches = self.patch_embed.num_patches

        # class token and position embeddings
        self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
        self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim))
        self.dropout = nn.Dropout(dropout)

        self.transformer_blocks = nn.ModuleList([
            TransformerBlock(embed_dim, num_heads, mlp_ratio, dropout)
            for _ in range(depth)
        ])

        # normalization and classification head
        self.norm = nn.LayerNorm(embed_dim)
        self.classifier = nn.Linear(embed_dim, num_classes)

        self._init_weights()
    
    def _init_weights(self):
        nn.init.normal_(self.pos_embed, std=0.02)
        nn.init.normal_(self.cls_token, std=0.02)

    def forward(self, x):
        batch_size = x.shape[0]
        
        x = self.patch_embed(x)
        
        cls_tokens = self.cls_token.expand(batch_size, -1, -1)
        x = torch.cat((cls_tokens, x), dim=1)
        
        x = x + self.pos_embed
        x = self.dropout(x)
        
        for block in self.transformer_blocks:
            x = block(x)

        # take only the cls token for classification
        x = self.norm(x)
        x = x[:, 0]  
        x = self.classifier(x)
        
        return x
    
def test_vision_transformer():
    image_size = 224
    patch_size = 16
    in_channels = 3
    num_classes = 1000
    batch_size = 4
    embed_dim = 768

    # random input
    x = torch.randn(batch_size, in_channels, image_size, image_size)
    
    model = VisionTransformer(
        image_size=image_size,
        patch_size=patch_size,
        in_channels=in_channels,
        num_classes=num_classes,
        embed_dim=embed_dim,
        depth=12,
        num_heads=12,
        mlp_ratio=4.0,
        dropout=0.1
    )

    # Test forward pass
    with torch.no_grad():
        output = model(x)

    # Verify shapes
    num_patches = (image_size // patch_size) ** 2
    expected_patch_embed_shape = (batch_size, num_patches, embed_dim)
    patch_embed_output = model.patch_embed(x)
    
    print("\nVision Transformer Test Results:")
    print(f"Input shape: {x.shape}")
    print(f"Patch embedding shape: {patch_embed_output.shape}")
    print(f"Final output shape: {output.shape}")

    assert patch_embed_output.shape == expected_patch_embed_shape, \
        f"Expected patch embedding shape {expected_patch_embed_shape}, got {patch_embed_output.shape}"
    assert output.shape == (batch_size, num_classes), \
        f"Expected output shape {(batch_size, num_classes)}, got {output.shape}"
    
    print("\nAll tests passed successfully!")
    
    return model, output

if __name__ == "__main__":
    model, output = test_vision_transformer()


Cite this Explanation

@article{ailinkdeeptech2025visiontransformeralgo,
  title={Vision Transformer (ViT) Implementation in PyTorch},
  author={AILinkDeepTech},
  journal={AILinkDeepTech Algorithm Explanations},
  year={2025},
  url={https://ailinkdeeptech.com/research/visiontransformer_algo}
}

Related Explanations