Loading your learning…
Loading your learning…
Now begin with token features x [B, T, E]. Learned projections create queries, keys, and values; split E across H heads so D=E/H. Rotate queries and keys before computing their scores. This runnable module returns contextual token features [B, T, E]. It is one attention layer, not a complete language model.
import torchfrom torch import nn def apply_rope(x, positions, base=10_000): # x: [B, H, T, D]; positions: [T]. D must be even. if x.shape[-1] % 2: raise ValueError("RoPE needs an even head dimension") half = x.shape[-1] // 2 frequencies = base ** (-torch.arange(half, device=x.device).float() / half) angles = positions.to(device=x.device, dtype=torch.float32)[:, None] * frequencies cos = angles.cos().to(x.dtype)[None, None, :, :] sin = angles.sin().to(x.dtype)[None, None, :, :] first, second = x[..., :half], x[..., half:] return torch.cat([first * cos - second * sin, first * sin + second * cos], dim=-1) class RotaryAttention(nn.Module): def __init__(self, dim, heads): super().__init__() if dim % heads or (dim // heads) % 2: raise ValueError("Each head needs an even dimension") self.heads = heads self.qkv = nn.Linear(dim, 3 * dim, bias=False) self.output = nn.Linear(dim, dim, bias=False) def forward(self, x): batch, length, dim = x.shape # [B, T, E] qkv = self.qkv(x).view(batch, length, 3, self.heads, dim // self.heads) q, k, v = qkv.permute(2, 0, 3, 1, 4).unbind(0) # each [B, H, T, D] positions = torch.arange(length, device=x.device) q, k = apply_rope(q, positions), apply_rope(k, positions) scores = q @ k.transpose(-2, -1) * (q.shape[-1] ** -0.5) future = torch.ones(length, length, device=x.device, dtype=torch.bool).triu(1) weights = scores.masked_fill(future, float("-inf")).softmax(dim=-1) mixed = (weights @ v).transpose(1, 2).reshape(batch, length, dim) return self.output(mixed) # [B, T, E] if __name__ == "__main__": torch.manual_seed(0) attention = RotaryAttention(dim=8, heads=2) x = torch.randn(2, 3, 8) # two examples, three tokens, eight features output = attention(x) print("Attention output:", output.shape) # [2, 3, 8]RotaryAttention owns learned Q/K/V and output projections. E must divide evenly across heads, and each head’s D must be even. In the main section E=8 and H=2, so D=4. The fixed RoPE frequencies need no learned positional table.
first * sin + second * cos], dim=-1) class RotaryAttention(nn.Module): def __init__(self, dim, heads): super().__init__() if dim % heads or (dim // heads) % 2: raise ValueError("Each head needs an even dimension") self.heads = heads self.qkv = nn.Linear(dim, 3 * dim, bias=False) self.output = nn.Linear(dim, dim, bias=False) def forward(self, x):qkv starts at [B, T, 3E]. view separates Q/K/V and heads into [B, T, 3, H, D]. permute reorders this to [3, B, H, T, D], and unbind removes the leading three-way axis to give q, k, and v. Every example and token is processed in one call.
def forward(self, x): batch, length, dim = x.shape # [B, T, E] qkv = self.qkv(x).view(batch, length, 3, self.heads, dim // self.heads) q, k, v = qkv.permute(2, 0, 3, 1, 4).unbind(0) # each [B, H, T, D] positions = torch.arange(length, device=x.device) q, k = apply_rope(q, positions), apply_rope(k, positions)