Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

rope.py

14_advanced_positional_embeddings/rope.py  ·  113 lines  ·  view on GitHub

"""
RoPE: Rotary Position Embedding
Interview question: "Implement RoPE"
"""
import numpy as np

def apply_rope(q: np.ndarray, k: np.ndarray, position: int, 
               dim: int, base: float = 10000.0) -> tuple:
    """
    Apply RoPE to query and key
    
    RoPE rotates Q and K by position-dependent angles
    This encodes relative position information
    
    Args:
        q: Query vector (d_model,)
        k: Key vector (d_model,)
        position: Current position
        dim: Dimension of model
        base: Base for frequency calculation
    Returns:
        (q_rotated, k_rotated)
    """
    # Create rotation angles based on position
    # Different frequencies for different dimensions
    inv_freq = 1.0 / (base ** (np.arange(0, dim, 2) / dim))
    angles = position * inv_freq
    
    # Split into pairs for rotation
    q_rot = np.zeros_like(q)
    k_rot = np.zeros_like(k)
    
    # Apply rotation to pairs
    for i in range(0, dim, 2):
        if i + 1 < dim:
            cos_angle = np.cos(angles[i // 2])
            sin_angle = np.sin(angles[i // 2])
            
            # Rotate pair (2D rotation)
            q_rot[i] = q[i] * cos_angle - q[i+1] * sin_angle
            q_rot[i+1] = q[i] * sin_angle + q[i+1] * cos_angle
            
            k_rot[i] = k[i] * cos_angle - k[i+1] * sin_angle
            k_rot[i+1] = k[i] * sin_angle + k[i+1] * cos_angle
        else:
            # Odd dimension, no rotation
            q_rot[i] = q[i]
            k_rot[i] = k[i]
    
    return q_rot, k_rot


def rope_attention(Q: np.ndarray, K: np.ndarray, V: np.ndarray,
                   positions: np.ndarray, d_k: int) -> np.ndarray:
    """
    Attention with RoPE
    
    Args:
        Q: Queries (seq_len, d_model)
        K: Keys (seq_len, d_model)
        V: Values (seq_len, d_model)
        positions: Position indices (seq_len,)
        d_k: Key dimension
    """
    seq_len, d_model = Q.shape
    output = np.zeros_like(Q)
    
    for i in range(seq_len):
        q_i = Q[i]
        # Apply RoPE to Q and K at position i
        q_rot, _ = apply_rope(q_i, q_i, positions[i], d_model)
        
        # For each key position
        scores = np.zeros(seq_len)
        for j in range(seq_len):
            k_j = K[j]
            _, k_rot = apply_rope(k_j, k_j, positions[j], d_model)
            
            # Compute attention score with rotated Q and K
            scores[j] = q_rot @ k_rot / np.sqrt(d_k)
        
        # Softmax
        exp_scores = np.exp(scores - np.max(scores))
        attention_weights = exp_scores / np.sum(exp_scores)
        
        # Apply to values
        output[i] = attention_weights @ V
    
    return output


# Usage Example
if __name__ == "__main__":
    print("RoPE Example")
    print("=" * 60)
    
    d_model = 64
    seq_len = 10
    
    # Random Q, K, V
    np.random.seed(42)
    Q = np.random.randn(seq_len, d_model)
    K = np.random.randn(seq_len, d_model)
    V = np.random.randn(seq_len, d_model)
    positions = np.arange(seq_len)
    
    # Apply RoPE attention
    output = rope_attention(Q, K, V, positions, d_model)
    
    print(f"Input shape: Q={Q.shape}, K={K.shape}, V={V.shape}")
    print(f"Output shape: {output.shape}")
    print(f"RoPE applied to positions 0-{seq_len-1}")