Skip to content
AILinkDeepTech
Go back
Deep Learning Medium

Rotary Position Embedding (RoPE) Implementation in PyTorch

Abstract

A PyTorch implementation of Rotary Position Embedding (RoPE): inverse frequency buffer, precomputed cos/sin cache, query/key rotation, and shape verification.

Rotary Position Embedding (RoPE) Implementation in PyTorch

This implementation builds Rotary Position Embedding (RoPE) from scratch. It includes an inverse frequency buffer, a precomputed cos/sin cache with auto-extension, and the apply_rotary_pos_emb helper that rotates query and key tensors before attention.

import torch
import torch.nn as nn

def rotate_half(x):
    x1 = x[..., : x.shape[-1] // 2]
    x2 = x[..., x.shape[-1] // 2 :]
    return torch.cat((-x2, x1), dim=-1)

def apply_rotary_pos_emb(q, k, cos, sin, position_ids, unsqueeze_dim=1):
    # cos: Cosine part of rotary embeddings
    # sin: Sine part of rotary embeddings
    # position_ids: Position indices for tokens
    cos = cos[position_ids].unsqueeze(unsqueeze_dim)
    sin = sin[position_ids].unsqueeze(unsqueeze_dim)
    
    b, h, s, d = q.shape
    q = q.view(b, h, s, d // 2, 2).transpose(4, 3).reshape(b, h, s, d)
    k = k.view(b, h, s, d // 2, 2).transpose(4, 3).reshape(b, h, s, d)
    
    q_embed = (q * cos) + (rotate_half(q) * sin)
    k_embed = (k * cos) + (rotate_half(k) * sin)
    return q_embed, k_embed

class RotaryPositionEmbedding(nn.Module):
    def __init__(self, dim, max_position_embeddings=2048, base=10000, device=None):
        super().__init__()

        self.dim = dim
        self.max_position_embeddings = max_position_embeddings
        self.base = base
        self.max_seq_len_cached = None
        
        if device is None:
            device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
            
        # inverse frequency buffer
        inv_freq = 1.0 / (self.base ** (torch.arange(0, dim, 2, dtype=torch.float32, device=device) / dim))
        self.register_buffer('inv_freq', inv_freq, persistent=False)

        self._update_cos_sin_cache(
            seq_len=max_position_embeddings,
            device=device,
            dtype=torch.float32
        )

    def _update_cos_sin_cache(self, seq_len, device, dtype):
        self.max_seq_len_cached = seq_len
        
        t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)
        
        # Compute frequency products
        freqs = torch.outer(t, self.inv_freq)
        
        emb = torch.cat((freqs, freqs), dim=-1)
        self.register_buffer('cos_cached', emb.cos().to(dtype), persistent=False)
        self.register_buffer('sin_cached', emb.sin().to(dtype), persistent=False)

    def forward(self, x, seq_len=None):
        if seq_len is None:
            seq_len = x.shape[-2]
        
        if self.max_seq_len_cached is None or seq_len > self.max_seq_len_cached:
            self._update_cos_sin_cache(
                seq_len=seq_len,
                device=x.device,
                dtype=x.dtype
            )
        
        return (
            self.cos_cached[:seq_len].to(dtype=x.dtype),
            self.sin_cached[:seq_len].to(dtype=x.dtype)
        )
    
def test_rope():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    
    batch_size = 2
    num_heads = 4
    seq_length = 16
    head_dim = 64

    rope = RotaryPositionEmbedding(
        dim=head_dim,
        max_position_embeddings=2048,
        device=device
    )
    
    q = torch.randn(batch_size, num_heads, seq_length, head_dim, device=device)
    k = torch.randn(batch_size, num_heads, seq_length, head_dim, device=device)
    
    position_ids = torch.arange(seq_length, device=device).unsqueeze(0).expand(batch_size, -1)

    cos, sin = rope(q, seq_len=seq_length)
    
    q_rotated, k_rotated = apply_rotary_pos_emb(q, k, cos, sin, position_ids)
    
    assert q_rotated.shape == q.shape, f"Query shape mismatch: {q_rotated.shape} != {q.shape}"
    assert k_rotated.shape == k.shape, f"Key shape mismatch: {k_rotated.shape} != {k.shape}"

    pos_0_q = q_rotated[:, :, 0, :]
    pos_1_q = q_rotated[:, :, 1, :]
    assert not torch.allclose(pos_0_q, pos_1_q), "Position 0 and 1 produce same embeddings"

    print("All tests passed!")
    return q_rotated, k_rotated

if __name__ == "__main__":
    q_rotated, k_rotated = test_rope()


Cite this Explanation

@article{ailinkdeeptech2026ropealgo,
  title={Rotary Position Embedding (RoPE) Implementation in PyTorch},
  author={AILinkDeepTech},
  journal={AILinkDeepTech Algorithm Explanations},
  year={2026},
  url={https://ailinkdeeptech.com/research/rope_algo}
}

Related Explanations