Download model/rotary_embedding.py from bobbydeveloper2014/forge-500m-chat: direct link, hf CLI and curl.
- Browser
- Download file 6.37 kB
-
https://huggingface.co/bobbydeveloper2014/forge-500m-chat/resolve/main/model/rotary_embedding.py
- Command line
-
hf download hf://bobbydeveloper2014/forge-500m-chat/model/rotary_embedding.py
-
curl -L -o rotary_embedding.py https://huggingface.co/bobbydeveloper2014/forge-500m-chat/resolve/main/model/rotary_embedding.py
6.37 kB
| """ | |
| RoPE (Rotary Positional Embedding) with YaRN support. | |
| GPU-optimized via cached cos/sin buffers and optional Triton kernel. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| import torch | |
| import torch.nn as nn | |
| from typing import Optional, Tuple | |
| class RotaryEmbedding(nn.Module): | |
| """ | |
| Rotary Positional Embedding (RoPE) with: | |
| - Long-context theta (500,000 base) | |
| - YaRN scaling for context extension | |
| - GPU-resident cos/sin cache (no recomputation per step) | |
| - BF16-native computation | |
| """ | |
| def __init__( | |
| self, | |
| head_dim: int, | |
| max_seq_len: int = 4096, | |
| base: float = 500_000.0, | |
| scaling_type: Optional[str] = None, | |
| scaling_factor: float = 1.0, | |
| device: Optional[torch.device] = None, | |
| ): | |
| super().__init__() | |
| self.head_dim = head_dim | |
| self.max_seq_len = max_seq_len | |
| self.base = base | |
| self.scaling_type = scaling_type | |
| self.scaling_factor = scaling_factor | |
| # Compute inverse frequencies | |
| inv_freq = self._build_inv_freq(head_dim, base, scaling_type, scaling_factor) | |
| self.register_buffer("inv_freq", inv_freq, persistent=False) | |
| # Pre-compute and cache cos/sin for all positions | |
| self._cos_cache: Optional[torch.Tensor] = None | |
| self._sin_cache: Optional[torch.Tensor] = None | |
| self._cache_seq_len: int = 0 | |
| # Build initial cache | |
| self._build_cache(max_seq_len, device) | |
| def _build_inv_freq( | |
| self, | |
| head_dim: int, | |
| base: float, | |
| scaling_type: Optional[str], | |
| scaling_factor: float, | |
| ) -> torch.Tensor: | |
| """Compute inverse frequencies with optional YaRN scaling.""" | |
| dim = head_dim | |
| # Standard RoPE frequencies | |
| freqs = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim)) | |
| if scaling_type == "yarn": | |
| # YaRN: scale only the low-frequency dimensions | |
| # High-frequency dims (short-range) are left unchanged | |
| # Low-frequency dims (long-range) are scaled down by 1/factor | |
| mscale = 0.1 * math.log(scaling_factor) + 1.0 # YaRN mscale | |
| # Threshold: dims where wavelength > original_max_len | |
| low_freq_factor = 1.0 | |
| high_freq_factor = 4.0 | |
| original_max_len = self.max_seq_len | |
| new_base = base * ( | |
| (scaling_factor * original_max_len / (2 * math.pi)) - | |
| (original_max_len / (2 * math.pi)) | |
| ) ** (dim / (dim - 2)) | |
| # Apply NTK-by-parts | |
| d = torch.arange(0, dim, 2).float() | |
| wavelength = 2 * math.pi / freqs | |
| # Smooth interpolation between no-scale and full-scale | |
| scale = torch.where( | |
| wavelength < original_max_len / high_freq_factor, | |
| torch.ones_like(freqs), | |
| torch.where( | |
| wavelength > original_max_len / low_freq_factor, | |
| torch.full_like(freqs, 1.0 / scaling_factor), | |
| (original_max_len / wavelength / low_freq_factor - 1.0) / | |
| (high_freq_factor / low_freq_factor - 1.0) * | |
| (1.0 - 1.0 / scaling_factor) + 1.0 / scaling_factor | |
| ) | |
| ) | |
| freqs = freqs * scale | |
| elif scaling_type == "linear": | |
| freqs = freqs / scaling_factor | |
| return freqs | |
| def _build_cache(self, seq_len: int, device=None): | |
| """Build/extend the cos/sin cache up to seq_len.""" | |
| if seq_len <= self._cache_seq_len: | |
| return | |
| device = device or (self.inv_freq.device if hasattr(self, 'inv_freq') | |
| else torch.device('cpu')) | |
| t = torch.arange(seq_len, device=device, dtype=torch.float32) | |
| freqs = torch.outer(t, self.inv_freq.to(device)) | |
| emb = torch.cat([freqs, freqs], dim=-1) # (seq_len, head_dim) | |
| # Store in BF16 for fast loading | |
| self._cos_cache = emb.cos().to(torch.bfloat16) | |
| self._sin_cache = emb.sin().to(torch.bfloat16) | |
| self._cache_seq_len = seq_len | |
| def get_cos_sin(self, seq_len: int, device: torch.device) -> Tuple[torch.Tensor, torch.Tensor]: | |
| """Return cached cos/sin for positions 0..seq_len-1.""" | |
| if seq_len > self._cache_seq_len: | |
| self._build_cache(seq_len * 2, device) # extend with buffer | |
| cos = self._cos_cache[:seq_len].to(device) | |
| sin = self._sin_cache[:seq_len].to(device) | |
| return cos, sin # each (seq_len, head_dim) | |
| def apply_rotary( | |
| self, | |
| q: torch.Tensor, # (B, n_heads, T, head_dim) | |
| k: torch.Tensor, # (B, n_kv_heads, T, head_dim) | |
| position_ids: Optional[torch.Tensor] = None, | |
| ) -> Tuple[torch.Tensor, torch.Tensor]: | |
| """Apply RoPE to Q and K.""" | |
| T = q.shape[2] | |
| device = q.device | |
| cos, sin = self.get_cos_sin(T, device) # (T, head_dim) | |
| if position_ids is not None: | |
| # Custom positions (for packed sequences or position bias) | |
| cos = cos[position_ids] # (B, T, head_dim) or (T, head_dim) | |
| sin = sin[position_ids] | |
| if position_ids.dim() == 2: | |
| cos = cos.unsqueeze(1) # (B, 1, T, head_dim) | |
| sin = sin.unsqueeze(1) | |
| else: | |
| cos = cos.unsqueeze(0).unsqueeze(0) # (1, 1, T, head_dim) | |
| sin = sin.unsqueeze(0).unsqueeze(0) | |
| else: | |
| # Reshape for broadcasting: (1, 1, T, head_dim) | |
| cos = cos.unsqueeze(0).unsqueeze(0) | |
| sin = sin.unsqueeze(0).unsqueeze(0) | |
| q_out = _rotate_half_apply(q.float(), cos, sin).to(q.dtype) | |
| k_out = _rotate_half_apply(k.float(), cos, sin).to(k.dtype) | |
| return q_out, k_out | |
| def forward(self, q, k, position_ids=None): | |
| return self.apply_rotary(q, k, position_ids) | |
| def _rotate_half_apply(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor: | |
| """Apply rotary: x * cos + rotate_half(x) * sin""" | |
| d = x.shape[-1] // 2 | |
| x1, x2 = x[..., :d], x[..., d:] | |
| rotated = torch.cat([-x2, x1], dim=-1) | |
| return x * cos + rotated * sin |