torchembed
torchembed is a single, well-tested, pip-installable home for modern
PyTorch embedding strategies — the ones missing from torch.nn. torch.nn
gives you nn.Embedding, a lookup table, and nothing else; the moment you
work with continuous inputs, transformer positional encodings, coordinates,
time, or tabular data, you're on your own. torchembed covers all of it in
one library, with an optional fused Triton kernel for RoPE on GPU.
Every embedding is a plain nn.Module: no required dependencies beyond
PyTorch, no .cuda() calls baked in, and no framework lock-in — use one
class or all of them.
Features
- Positional embeddings —
RotaryEmbedding(RoPE, LLaMA/Mistral-style),ALiBiEmbedding(long-context extrapolation),SinusoidalEmbedding,LearnedPositionalEmbedding. - Fourier features —
RandomFourierFeatures(coordinate/kernel encoding),LearnedFourierFeatures,GaussianFourierProjection(diffusion timestep embedding). - Categorical embeddings —
EntityEmbeddingandMultiCategoricalEmbeddingfor tabular data, with auto-sized embedding dimensions. - Patch embeddings —
PatchEmbedding(ViT) andTubeletEmbedding(video transformers: VideoMAE, ViViT). - Temporal embeddings —
CyclicEmbedding,TimestampEmbedding,FrequencyEmbeddingfor hour/day/month and periodic time series. - Fused Triton kernels — optional GPU-accelerated RoPE forward and
backward, ~4x faster than plain PyTorch and ~2x faster than
torch.compile, with automatic CPU fallback when triton isn't installed.
1"""**torchembed** is a single, well-tested, pip-installable home for modern 2PyTorch embedding strategies — the ones missing from `torch.nn`. `torch.nn` 3gives you `nn.Embedding`, a lookup table, and nothing else; the moment you 4work with continuous inputs, transformer positional encodings, coordinates, 5time, or tabular data, you're on your own. torchembed covers all of it in 6one library, with an optional fused Triton kernel for RoPE on GPU. 7 8Every embedding is a plain `nn.Module`: no required dependencies beyond 9PyTorch, no `.cuda()` calls baked in, and no framework lock-in — use one 10class or all of them. 11 12## Features 13 14- **Positional embeddings** — `RotaryEmbedding` (RoPE, LLaMA/Mistral-style), 15 `ALiBiEmbedding` (long-context extrapolation), `SinusoidalEmbedding`, 16 `LearnedPositionalEmbedding`. 17- **Fourier features** — `RandomFourierFeatures` (coordinate/kernel 18 encoding), `LearnedFourierFeatures`, `GaussianFourierProjection` 19 (diffusion timestep embedding). 20- **Categorical embeddings** — `EntityEmbedding` and 21 `MultiCategoricalEmbedding` for tabular data, with auto-sized embedding 22 dimensions. 23- **Patch embeddings** — `PatchEmbedding` (ViT) and `TubeletEmbedding` 24 (video transformers: VideoMAE, ViViT). 25- **Temporal embeddings** — `CyclicEmbedding`, `TimestampEmbedding`, 26 `FrequencyEmbedding` for hour/day/month and periodic time series. 27- **Fused Triton kernels** — optional GPU-accelerated RoPE forward and 28 backward, ~4x faster than plain PyTorch and ~2x faster than 29 `torch.compile`, with automatic CPU fallback when triton isn't installed. 30""" 31 32from torchembed.categorical import EntityEmbedding, MultiCategoricalEmbedding 33from torchembed.fourier import ( 34 GaussianFourierProjection, 35 LearnedFourierFeatures, 36 RandomFourierFeatures, 37) 38from torchembed.patch import PatchEmbedding, TubeletEmbedding 39from torchembed.positional import ( 40 ALiBiEmbedding, 41 LearnedPositionalEmbedding, 42 RotaryEmbedding, 43 SinusoidalEmbedding, 44) 45from torchembed.temporal import CyclicEmbedding, FrequencyEmbedding, TimestampEmbedding 46 47__all__ = [ 48 "RotaryEmbedding", 49 "ALiBiEmbedding", 50 "SinusoidalEmbedding", 51 "LearnedPositionalEmbedding", 52 "RandomFourierFeatures", 53 "LearnedFourierFeatures", 54 "GaussianFourierProjection", 55 "EntityEmbedding", 56 "MultiCategoricalEmbedding", 57 "PatchEmbedding", 58 "TubeletEmbedding", 59 "CyclicEmbedding", 60 "TimestampEmbedding", 61 "FrequencyEmbedding", 62]
20class RotaryEmbedding(nn.Module): 21 """Rotary Position Embedding (RoPE). 22 23 Encodes position by rotating query and key vectors in 2D subspaces. 24 Unlike additive embeddings, RoPE is applied directly to Q and K inside 25 the attention layer, not to the input sequence. 26 27 Used in: LLaMA, Mistral, Falcon, PaLM, GPT-NeoX, and most modern LLMs. 28 29 Reference: 30 Su et al., "RoFormer: Enhanced Transformer with Rotary Position Embedding" 31 https://arxiv.org/abs/2104.09864 32 33 Args: 34 dim: Head dimension (must be even). Typically d_model // num_heads. 35 max_seq_len: Maximum sequence length to precompute. Longer sequences 36 will be computed on the fly. 37 base: Base for the geometric progression of frequencies. Default 10000 38 matches the original paper. LLaMA 3 uses 500000. 39 use_fused: If True, uses a fused triton kernel for the forward pass 40 (requires GPU and ``triton``). Default False. 41 device: Device to create buffers on. 42 43 Example:: 44 45 rope = RotaryEmbedding(dim=64) 46 q = torch.randn(2, 8, 16, 64) # (batch, heads, seq, dim) 47 k = torch.randn(2, 8, 16, 64) 48 q_rot, k_rot = rope(q, k) 49 """ 50 51 def __init__( 52 self, 53 dim: int, 54 max_seq_len: int = 2048, 55 base: int = 10_000, 56 use_fused: bool = False, 57 device: Optional[torch.device] = None, 58 ) -> None: 59 super().__init__() 60 if dim % 2 != 0: 61 raise ValueError(f"dim must be even, got {dim}") 62 63 self.dim = dim 64 self.max_seq_len = max_seq_len 65 self.base = base 66 self.use_fused = use_fused 67 68 # Precompute inverse frequencies: shape (dim/2,) 69 inv_freq = 1.0 / ( 70 base ** (torch.arange(0, dim, 2, device=device).float() / dim) 71 ) # noqa: E501 72 self.register_buffer("inv_freq", inv_freq, persistent=False) 73 74 # Precompute cos/sin cache 75 self._build_cache(max_seq_len, device) 76 77 def _build_cache(self, seq_len: int, device: Optional[torch.device] = None) -> None: 78 t = torch.arange(seq_len, device=device or self.inv_freq.device).float() 79 freqs = torch.outer(t, self.inv_freq) # (seq_len, dim/2) 80 emb = torch.cat([freqs, freqs], dim=-1) # (seq_len, dim) 81 self.register_buffer("cos_cache", emb.cos(), persistent=False) 82 self.register_buffer("sin_cache", emb.sin(), persistent=False) 83 84 @staticmethod 85 def _rotate_half(x: Tensor) -> Tensor: 86 """Rotate the last dimension by splitting and negating halves.""" 87 x1, x2 = x.chunk(2, dim=-1) 88 return torch.cat([-x2, x1], dim=-1) 89 90 def _vanilla_forward( 91 self, q: Tensor, k: Tensor, cos: Tensor, sin: Tensor 92 ) -> tuple[Tensor, Tensor]: # noqa: E501 93 cos = cos.unsqueeze(0).unsqueeze(0) 94 sin = sin.unsqueeze(0).unsqueeze(0) 95 q_rot = q * cos + self._rotate_half(q) * sin 96 k_rot = k * cos + self._rotate_half(k) * sin 97 return q_rot, k_rot 98 99 def _fused_forward( 100 self, q: Tensor, k: Tensor, cos: Tensor, sin: Tensor 101 ) -> tuple[Tensor, Tensor]: # noqa: E501 102 from torchembed._triton import fused_rope_forward 103 104 return fused_rope_forward(q, k, cos, sin) 105 106 def forward(self, q: Tensor, k: Tensor, seq_dim: int = -2) -> tuple[Tensor, Tensor]: 107 """Apply rotary embeddings to query and key tensors. 108 109 Args: 110 q: Query tensor of shape (..., seq_len, dim). 111 k: Key tensor of shape (..., seq_len, dim). 112 seq_dim: Dimension along which sequence length lives. Default -2. 113 114 Returns: 115 Tuple of (rotated_q, rotated_k) with the same shapes as inputs. 116 """ 117 seq_len = q.shape[seq_dim] 118 119 if seq_len > self.max_seq_len: 120 self._build_cache(seq_len, q.device) 121 self.max_seq_len = seq_len 122 123 cos = self.cos_cache[:seq_len].to(device=q.device) 124 sin = self.sin_cache[:seq_len].to(device=q.device) 125 126 if self.use_fused and q.is_cuda and k.is_cuda: 127 try: 128 return self._fused_forward(q, k, cos, sin) 129 except (ImportError, RuntimeError): 130 pass 131 132 return self._vanilla_forward(q, k, cos, sin)
Rotary Position Embedding (RoPE).
Encodes position by rotating query and key vectors in 2D subspaces. Unlike additive embeddings, RoPE is applied directly to Q and K inside the attention layer, not to the input sequence.
Used in: LLaMA, Mistral, Falcon, PaLM, GPT-NeoX, and most modern LLMs.
Reference:
Su et al., "RoFormer: Enhanced Transformer with Rotary Position Embedding" https://arxiv.org/abs/2104.09864
Arguments:
- dim: Head dimension (must be even). Typically d_model // num_heads.
- max_seq_len: Maximum sequence length to precompute. Longer sequences will be computed on the fly.
- base: Base for the geometric progression of frequencies. Default 10000 matches the original paper. LLaMA 3 uses 500000.
- use_fused: If True, uses a fused triton kernel for the forward pass
(requires GPU and
triton). Default False. - device: Device to create buffers on.
Example::
rope = RotaryEmbedding(dim=64)
q = torch.randn(2, 8, 16, 64) # (batch, heads, seq, dim)
k = torch.randn(2, 8, 16, 64)
q_rot, k_rot = rope(q, k)
51 def __init__( 52 self, 53 dim: int, 54 max_seq_len: int = 2048, 55 base: int = 10_000, 56 use_fused: bool = False, 57 device: Optional[torch.device] = None, 58 ) -> None: 59 super().__init__() 60 if dim % 2 != 0: 61 raise ValueError(f"dim must be even, got {dim}") 62 63 self.dim = dim 64 self.max_seq_len = max_seq_len 65 self.base = base 66 self.use_fused = use_fused 67 68 # Precompute inverse frequencies: shape (dim/2,) 69 inv_freq = 1.0 / ( 70 base ** (torch.arange(0, dim, 2, device=device).float() / dim) 71 ) # noqa: E501 72 self.register_buffer("inv_freq", inv_freq, persistent=False) 73 74 # Precompute cos/sin cache 75 self._build_cache(max_seq_len, device)
Initialize internal Module state, shared by both nn.Module and ScriptModule.
106 def forward(self, q: Tensor, k: Tensor, seq_dim: int = -2) -> tuple[Tensor, Tensor]: 107 """Apply rotary embeddings to query and key tensors. 108 109 Args: 110 q: Query tensor of shape (..., seq_len, dim). 111 k: Key tensor of shape (..., seq_len, dim). 112 seq_dim: Dimension along which sequence length lives. Default -2. 113 114 Returns: 115 Tuple of (rotated_q, rotated_k) with the same shapes as inputs. 116 """ 117 seq_len = q.shape[seq_dim] 118 119 if seq_len > self.max_seq_len: 120 self._build_cache(seq_len, q.device) 121 self.max_seq_len = seq_len 122 123 cos = self.cos_cache[:seq_len].to(device=q.device) 124 sin = self.sin_cache[:seq_len].to(device=q.device) 125 126 if self.use_fused and q.is_cuda and k.is_cuda: 127 try: 128 return self._fused_forward(q, k, cos, sin) 129 except (ImportError, RuntimeError): 130 pass 131 132 return self._vanilla_forward(q, k, cos, sin)
Apply rotary embeddings to query and key tensors.
Arguments:
- q: Query tensor of shape (..., seq_len, dim).
- k: Key tensor of shape (..., seq_len, dim).
- seq_dim: Dimension along which sequence length lives. Default -2.
Returns:
Tuple of (rotated_q, rotated_k) with the same shapes as inputs.
135class ALiBiEmbedding(nn.Module): 136 """Attention with Linear Biases (ALiBi). 137 138 Instead of adding positional information to token embeddings, ALiBi 139 adds a fixed, non-learned bias to attention scores that penalizes 140 distance between tokens linearly. This allows strong extrapolation 141 to longer sequences than seen during training. 142 143 Used in: BLOOM, MPT, and other long-context models. 144 145 Reference: 146 Press et al., "Train Short, Test Long: Attention with Linear Biases 147 Enables Input Length Extrapolation" https://arxiv.org/abs/2108.12409 148 149 Args: 150 num_heads: Number of attention heads. Each head gets a different slope. 151 max_seq_len: Maximum sequence length to precompute biases for. 152 153 Example:: 154 155 alibi = ALiBiEmbedding(num_heads=8) 156 # attn_scores: (batch, heads, seq, seq) 157 attn_scores = torch.randn(2, 8, 16, 16) 158 biased_scores = alibi(attn_scores) 159 """ 160 161 def __init__(self, num_heads: int, max_seq_len: int = 2048) -> None: 162 super().__init__() 163 self.num_heads = num_heads 164 165 slopes = self._get_slopes(num_heads) # (num_heads,) 166 bias = self._build_bias(slopes, max_seq_len) # (num_heads, seq, seq) 167 self.register_buffer("bias", bias, persistent=False) 168 169 @staticmethod 170 def _get_slopes(num_heads: int) -> Tensor: 171 """Compute ALiBi slopes following the original paper's geometric sequence.""" 172 # Nearest power of 2 >= num_heads 173 n = 2 ** math.ceil(math.log2(num_heads)) 174 slopes = torch.pow(2, -torch.arange(1, n + 1) * (8 / n)) 175 if n > num_heads: 176 # Interleave to handle non-power-of-2 head counts 177 slopes = torch.cat([slopes[1::2], slopes[::2]])[:num_heads] 178 return slopes 179 180 @staticmethod 181 def _build_bias(slopes: Tensor, max_seq_len: int) -> Tensor: 182 positions = torch.arange(max_seq_len) 183 # Relative distances: (seq, seq) lower-triangular distance matrix 184 dist = positions.unsqueeze(0) - positions.unsqueeze(1) # (seq, seq) 185 dist = -dist.abs() 186 # Scale by each head's slope: (num_heads, seq, seq) 187 bias = slopes.unsqueeze(-1).unsqueeze(-1) * dist.unsqueeze(0) 188 return bias 189 190 def forward(self, attn_scores: Tensor) -> Tensor: 191 """Add ALiBi positional bias to attention scores. 192 193 Args: 194 attn_scores: Attention logits of shape (batch, heads, seq_q, seq_k). 195 196 Returns: 197 Attention scores with ALiBi bias added, same shape as input. 198 """ 199 seq_len = attn_scores.shape[-1] 200 bias = self.bias[:, :seq_len, :seq_len] # (heads, seq, seq) 201 return attn_scores + bias.unsqueeze(0) # broadcast over batch
Attention with Linear Biases (ALiBi).
Instead of adding positional information to token embeddings, ALiBi adds a fixed, non-learned bias to attention scores that penalizes distance between tokens linearly. This allows strong extrapolation to longer sequences than seen during training.
Used in: BLOOM, MPT, and other long-context models.
Reference:
Press et al., "Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation" https://arxiv.org/abs/2108.12409
Arguments:
- num_heads: Number of attention heads. Each head gets a different slope.
- max_seq_len: Maximum sequence length to precompute biases for.
Example::
alibi = ALiBiEmbedding(num_heads=8)
# attn_scores: (batch, heads, seq, seq)
attn_scores = torch.randn(2, 8, 16, 16)
biased_scores = alibi(attn_scores)
161 def __init__(self, num_heads: int, max_seq_len: int = 2048) -> None: 162 super().__init__() 163 self.num_heads = num_heads 164 165 slopes = self._get_slopes(num_heads) # (num_heads,) 166 bias = self._build_bias(slopes, max_seq_len) # (num_heads, seq, seq) 167 self.register_buffer("bias", bias, persistent=False)
Initialize internal Module state, shared by both nn.Module and ScriptModule.
190 def forward(self, attn_scores: Tensor) -> Tensor: 191 """Add ALiBi positional bias to attention scores. 192 193 Args: 194 attn_scores: Attention logits of shape (batch, heads, seq_q, seq_k). 195 196 Returns: 197 Attention scores with ALiBi bias added, same shape as input. 198 """ 199 seq_len = attn_scores.shape[-1] 200 bias = self.bias[:, :seq_len, :seq_len] # (heads, seq, seq) 201 return attn_scores + bias.unsqueeze(0) # broadcast over batch
Add ALiBi positional bias to attention scores.
Arguments:
- attn_scores: Attention logits of shape (batch, heads, seq_q, seq_k).
Returns:
Attention scores with ALiBi bias added, same shape as input.
204class SinusoidalEmbedding(nn.Module): 205 """Fixed sinusoidal positional embedding from "Attention Is All You Need". 206 207 Adds a non-learned, frequency-based positional signal to input embeddings. 208 The encoding is deterministic and can generalize slightly beyond the 209 training sequence length. 210 211 Reference: 212 Vaswani et al., "Attention Is All You Need" https://arxiv.org/abs/1706.03762 213 214 Args: 215 dim: Embedding dimension (must be even). 216 max_seq_len: Maximum supported sequence length. 217 dropout: Optional dropout rate applied after adding the embedding. 218 learned_scale: If True, adds a single learned scalar to scale the 219 sinusoidal signal (a light touch of trainability). 220 221 Example:: 222 223 emb = SinusoidalEmbedding(dim=512) 224 x = torch.randn(2, 16, 512) # (batch, seq, dim) 225 x = emb(x) 226 """ 227 228 def __init__( 229 self, 230 dim: int, 231 max_seq_len: int = 4096, 232 dropout: float = 0.0, 233 learned_scale: bool = False, 234 ) -> None: 235 super().__init__() 236 if dim % 2 != 0: 237 raise ValueError(f"dim must be even, got {dim}") 238 239 self.dim = dim 240 self.dropout = nn.Dropout(p=dropout) if dropout > 0 else nn.Identity() 241 self.scale = nn.Parameter(torch.ones(1)) if learned_scale else None 242 243 pe = self._build_pe(dim, max_seq_len) # (1, max_seq_len, dim) 244 self.register_buffer("pe", pe, persistent=False) 245 246 @staticmethod 247 def _build_pe(dim: int, max_seq_len: int) -> Tensor: 248 position = torch.arange(max_seq_len).unsqueeze(1).float() 249 div_term = torch.exp( 250 torch.arange(0, dim, 2).float() * (-math.log(10000.0) / dim) 251 ) 252 pe = torch.zeros(1, max_seq_len, dim) 253 pe[0, :, 0::2] = torch.sin(position * div_term) 254 pe[0, :, 1::2] = torch.cos(position * div_term) 255 return pe 256 257 def forward(self, x: Tensor) -> Tensor: 258 """Add sinusoidal positional encoding to input. 259 260 Args: 261 x: Input tensor of shape (batch, seq_len, dim). 262 263 Returns: 264 Tensor of same shape with positional encoding added. 265 """ 266 seq_len = x.shape[1] 267 pe = self.pe[:, :seq_len, :] 268 if self.scale is not None: 269 pe = pe * self.scale 270 return self.dropout(x + pe)
Fixed sinusoidal positional embedding from "Attention Is All You Need".
Adds a non-learned, frequency-based positional signal to input embeddings. The encoding is deterministic and can generalize slightly beyond the training sequence length.
Reference:
Vaswani et al., "Attention Is All You Need" https://arxiv.org/abs/1706.03762
Arguments:
- dim: Embedding dimension (must be even).
- max_seq_len: Maximum supported sequence length.
- dropout: Optional dropout rate applied after adding the embedding.
- learned_scale: If True, adds a single learned scalar to scale the sinusoidal signal (a light touch of trainability).
Example::
emb = SinusoidalEmbedding(dim=512)
x = torch.randn(2, 16, 512) # (batch, seq, dim)
x = emb(x)
228 def __init__( 229 self, 230 dim: int, 231 max_seq_len: int = 4096, 232 dropout: float = 0.0, 233 learned_scale: bool = False, 234 ) -> None: 235 super().__init__() 236 if dim % 2 != 0: 237 raise ValueError(f"dim must be even, got {dim}") 238 239 self.dim = dim 240 self.dropout = nn.Dropout(p=dropout) if dropout > 0 else nn.Identity() 241 self.scale = nn.Parameter(torch.ones(1)) if learned_scale else None 242 243 pe = self._build_pe(dim, max_seq_len) # (1, max_seq_len, dim) 244 self.register_buffer("pe", pe, persistent=False)
Initialize internal Module state, shared by both nn.Module and ScriptModule.
257 def forward(self, x: Tensor) -> Tensor: 258 """Add sinusoidal positional encoding to input. 259 260 Args: 261 x: Input tensor of shape (batch, seq_len, dim). 262 263 Returns: 264 Tensor of same shape with positional encoding added. 265 """ 266 seq_len = x.shape[1] 267 pe = self.pe[:, :seq_len, :] 268 if self.scale is not None: 269 pe = pe * self.scale 270 return self.dropout(x + pe)
Add sinusoidal positional encoding to input.
Arguments:
- x: Input tensor of shape (batch, seq_len, dim).
Returns:
Tensor of same shape with positional encoding added.
273class LearnedPositionalEmbedding(nn.Module): 274 """Standard learned positional embedding. 275 276 A simple lookup table mapping each position index to a learnable vector. 277 Used in BERT, GPT-2, and many other models. Simpler than sinusoidal but 278 cannot extrapolate beyond the training sequence length. 279 280 Args: 281 max_seq_len: Maximum sequence length (vocabulary size for positions). 282 dim: Embedding dimension. 283 dropout: Optional dropout rate. 284 padding_idx: If set, the embedding at this index is not updated. 285 286 Example:: 287 288 emb = LearnedPositionalEmbedding(max_seq_len=512, dim=768) 289 x = torch.randn(2, 16, 768) 290 x = emb(x) 291 """ 292 293 def __init__( 294 self, 295 max_seq_len: int, 296 dim: int, 297 dropout: float = 0.0, 298 padding_idx: Optional[int] = None, 299 ) -> None: 300 super().__init__() 301 self.embedding = nn.Embedding(max_seq_len, dim, padding_idx=padding_idx) 302 self.dropout = nn.Dropout(p=dropout) if dropout > 0 else nn.Identity() 303 nn.init.normal_(self.embedding.weight, std=0.02) 304 305 def forward(self, x: Tensor, offset: int = 0) -> Tensor: 306 """Add learned positional embeddings to input. 307 308 Args: 309 x: Input tensor of shape (batch, seq_len, dim). 310 offset: Starting position index. Useful for KV-cache inference 311 where you process one token at a time. 312 313 Returns: 314 Tensor of same shape with positional embeddings added. 315 """ 316 seq_len = x.shape[1] 317 positions = torch.arange(offset, offset + seq_len, device=x.device) 318 return self.dropout(x + self.embedding(positions))
Standard learned positional embedding.
A simple lookup table mapping each position index to a learnable vector. Used in BERT, GPT-2, and many other models. Simpler than sinusoidal but cannot extrapolate beyond the training sequence length.
Arguments:
- max_seq_len: Maximum sequence length (vocabulary size for positions).
- dim: Embedding dimension.
- dropout: Optional dropout rate.
- padding_idx: If set, the embedding at this index is not updated.
Example::
emb = LearnedPositionalEmbedding(max_seq_len=512, dim=768)
x = torch.randn(2, 16, 768)
x = emb(x)
293 def __init__( 294 self, 295 max_seq_len: int, 296 dim: int, 297 dropout: float = 0.0, 298 padding_idx: Optional[int] = None, 299 ) -> None: 300 super().__init__() 301 self.embedding = nn.Embedding(max_seq_len, dim, padding_idx=padding_idx) 302 self.dropout = nn.Dropout(p=dropout) if dropout > 0 else nn.Identity() 303 nn.init.normal_(self.embedding.weight, std=0.02)
Initialize internal Module state, shared by both nn.Module and ScriptModule.
305 def forward(self, x: Tensor, offset: int = 0) -> Tensor: 306 """Add learned positional embeddings to input. 307 308 Args: 309 x: Input tensor of shape (batch, seq_len, dim). 310 offset: Starting position index. Useful for KV-cache inference 311 where you process one token at a time. 312 313 Returns: 314 Tensor of same shape with positional embeddings added. 315 """ 316 seq_len = x.shape[1] 317 positions = torch.arange(offset, offset + seq_len, device=x.device) 318 return self.dropout(x + self.embedding(positions))
Add learned positional embeddings to input.
Arguments:
- x: Input tensor of shape (batch, seq_len, dim).
- offset: Starting position index. Useful for KV-cache inference where you process one token at a time.
Returns:
Tensor of same shape with positional embeddings added.
18class RandomFourierFeatures(nn.Module): 19 """Random Fourier Features (RFF) for kernel approximation. 20 21 Maps input features to a randomized low-dimensional feature space such 22 that the inner product in the new space approximates a shift-invariant 23 kernel (e.g. RBF/Gaussian). Useful for scalable kernel methods and as 24 a fixed encoding for continuous inputs like coordinates. 25 26 Reference: 27 Rahimi & Recht, "Random Features for Large-Scale Kernel Machines" 28 https://papers.nips.cc/paper/2007/hash/013a006f03dbc5392effeb8f18fda755-Abstract.html 29 30 Args: 31 in_features: Input dimension. 32 out_features: Output dimension (number of random features). Should be 33 large enough to approximate the kernel well (e.g. 256–2048). 34 sigma: Bandwidth of the RBF kernel. Controls the frequency of the 35 random features. Smaller = higher frequency. 36 trainable: If True, the random projection matrix is a learnable parameter. 37 38 Example:: 39 40 rff = RandomFourierFeatures(in_features=2, out_features=256) 41 coords = torch.randn(32, 2) # e.g. 2D spatial coordinates 42 features = rff(coords) # (32, 256) 43 """ 44 45 def __init__( 46 self, 47 in_features: int, 48 out_features: int, 49 sigma: float = 1.0, 50 trainable: bool = False, 51 ) -> None: 52 super().__init__() 53 if out_features % 2 != 0: 54 raise ValueError(f"out_features must be even, got {out_features}") 55 56 self.in_features = in_features 57 self.out_features = out_features 58 self.sigma = sigma 59 60 # Random projection matrix: (in_features, out_features // 2) 61 W = torch.randn(in_features, out_features // 2) / sigma 62 if trainable: 63 self.W = nn.Parameter(W) 64 else: 65 self.register_buffer("W", W) 66 67 def forward(self, x: Tensor) -> Tensor: 68 """Project input to random Fourier feature space. 69 70 Args: 71 x: Input tensor of shape (..., in_features). 72 73 Returns: 74 Tensor of shape (..., out_features) with cosine and sine features 75 concatenated and scaled by sqrt(2 / out_features). 76 """ 77 projection = x @ self.W # (..., out_features // 2) 78 scale = math.sqrt(2.0 / self.out_features) 79 return scale * torch.cat([torch.cos(projection), torch.sin(projection)], dim=-1)
Random Fourier Features (RFF) for kernel approximation.
Maps input features to a randomized low-dimensional feature space such that the inner product in the new space approximates a shift-invariant kernel (e.g. RBF/Gaussian). Useful for scalable kernel methods and as a fixed encoding for continuous inputs like coordinates.
Reference:
Rahimi & Recht, "Random Features for Large-Scale Kernel Machines" https://papers.nips.cc/paper/2007/hash/013a006f03dbc5392effeb8f18fda755-Abstract.html
Arguments:
- in_features: Input dimension.
- out_features: Output dimension (number of random features). Should be large enough to approximate the kernel well (e.g. 256–2048).
- sigma: Bandwidth of the RBF kernel. Controls the frequency of the random features. Smaller = higher frequency.
- trainable: If True, the random projection matrix is a learnable parameter.
Example::
rff = RandomFourierFeatures(in_features=2, out_features=256)
coords = torch.randn(32, 2) # e.g. 2D spatial coordinates
features = rff(coords) # (32, 256)
45 def __init__( 46 self, 47 in_features: int, 48 out_features: int, 49 sigma: float = 1.0, 50 trainable: bool = False, 51 ) -> None: 52 super().__init__() 53 if out_features % 2 != 0: 54 raise ValueError(f"out_features must be even, got {out_features}") 55 56 self.in_features = in_features 57 self.out_features = out_features 58 self.sigma = sigma 59 60 # Random projection matrix: (in_features, out_features // 2) 61 W = torch.randn(in_features, out_features // 2) / sigma 62 if trainable: 63 self.W = nn.Parameter(W) 64 else: 65 self.register_buffer("W", W)
Initialize internal Module state, shared by both nn.Module and ScriptModule.
67 def forward(self, x: Tensor) -> Tensor: 68 """Project input to random Fourier feature space. 69 70 Args: 71 x: Input tensor of shape (..., in_features). 72 73 Returns: 74 Tensor of shape (..., out_features) with cosine and sine features 75 concatenated and scaled by sqrt(2 / out_features). 76 """ 77 projection = x @ self.W # (..., out_features // 2) 78 scale = math.sqrt(2.0 / self.out_features) 79 return scale * torch.cat([torch.cos(projection), torch.sin(projection)], dim=-1)
Project input to random Fourier feature space.
Arguments:
- x: Input tensor of shape (..., in_features).
Returns:
Tensor of shape (..., out_features) with cosine and sine features concatenated and scaled by sqrt(2 / out_features).
82class LearnedFourierFeatures(nn.Module): 83 """Learned Fourier Features. 84 85 A trainable variant of random Fourier features where both the frequency 86 matrix and a final linear projection are learned end-to-end. Useful when 87 you want the network to discover the right frequency decomposition for 88 your specific input domain. 89 90 Args: 91 in_features: Input dimension. 92 num_frequencies: Number of frequency components (output will be 93 2 * num_frequencies before projection). 94 out_features: Final output dimension after linear projection. 95 sigma_init: Initial scale for frequency initialization. 96 97 Example:: 98 99 lff = LearnedFourierFeatures( 100 in_features=3, num_frequencies=128, out_features=256 101 ) 102 x = torch.randn(16, 3) 103 features = lff(x) # (16, 256) 104 """ 105 106 def __init__( 107 self, 108 in_features: int, 109 num_frequencies: int, 110 out_features: int, 111 sigma_init: float = 1.0, 112 ) -> None: 113 super().__init__() 114 self.freq = nn.Parameter( 115 torch.randn(in_features, num_frequencies) / sigma_init 116 ) 117 self.proj = nn.Linear(2 * num_frequencies, out_features) 118 nn.init.xavier_uniform_(self.proj.weight) 119 120 def forward(self, x: Tensor) -> Tensor: 121 """Compute learned Fourier features. 122 123 Args: 124 x: Input tensor of shape (..., in_features). 125 126 Returns: 127 Tensor of shape (..., out_features). 128 """ 129 projection = x @ self.freq # (..., num_frequencies) 130 features = torch.cat([torch.cos(projection), torch.sin(projection)], dim=-1) 131 return self.proj(features)
Learned Fourier Features.
A trainable variant of random Fourier features where both the frequency matrix and a final linear projection are learned end-to-end. Useful when you want the network to discover the right frequency decomposition for your specific input domain.
Arguments:
- in_features: Input dimension.
- num_frequencies: Number of frequency components (output will be 2 * num_frequencies before projection).
- out_features: Final output dimension after linear projection.
- sigma_init: Initial scale for frequency initialization.
Example::
lff = LearnedFourierFeatures(
in_features=3, num_frequencies=128, out_features=256
)
x = torch.randn(16, 3)
features = lff(x) # (16, 256)
106 def __init__( 107 self, 108 in_features: int, 109 num_frequencies: int, 110 out_features: int, 111 sigma_init: float = 1.0, 112 ) -> None: 113 super().__init__() 114 self.freq = nn.Parameter( 115 torch.randn(in_features, num_frequencies) / sigma_init 116 ) 117 self.proj = nn.Linear(2 * num_frequencies, out_features) 118 nn.init.xavier_uniform_(self.proj.weight)
Initialize internal Module state, shared by both nn.Module and ScriptModule.
120 def forward(self, x: Tensor) -> Tensor: 121 """Compute learned Fourier features. 122 123 Args: 124 x: Input tensor of shape (..., in_features). 125 126 Returns: 127 Tensor of shape (..., out_features). 128 """ 129 projection = x @ self.freq # (..., num_frequencies) 130 features = torch.cat([torch.cos(projection), torch.sin(projection)], dim=-1) 131 return self.proj(features)
Compute learned Fourier features.
Arguments:
- x: Input tensor of shape (..., in_features).
Returns:
Tensor of shape (..., out_features).
134class GaussianFourierProjection(nn.Module): 135 """Gaussian Fourier Projection for continuous scalar embedding. 136 137 A widely used technique in diffusion models and score-based generative 138 models to embed continuous scalars (e.g. timestep t, noise level σ) 139 into a high-dimensional space. Uses fixed random frequencies scaled by 140 a learnable or fixed bandwidth. 141 142 Reference: 143 Song et al., "Score-Based Generative Modeling through Stochastic 144 Differential Equations" https://arxiv.org/abs/2011.13456 145 146 Args: 147 embed_dim: Output embedding dimension. Must be even. 148 scale: Scale factor for the random frequencies. Controls how quickly 149 the embedding oscillates. Typical values: 16–30. 150 learnable: If True, the random weights are trainable. 151 152 Example:: 153 154 gfp = GaussianFourierProjection(embed_dim=256, scale=16) 155 t = torch.rand(32) # continuous timesteps in [0, 1] 156 emb = gfp(t) # (32, 256) 157 158 # Common usage in diffusion models: 159 # t_emb = gfp(t) 160 # then feed through an MLP to condition the UNet 161 """ 162 163 def __init__( 164 self, 165 embed_dim: int, 166 scale: float = 16.0, 167 learnable: bool = False, 168 ) -> None: 169 super().__init__() 170 if embed_dim % 2 != 0: 171 raise ValueError(f"embed_dim must be even, got {embed_dim}") 172 173 W = torch.randn(embed_dim // 2) * scale 174 if learnable: 175 self.W = nn.Parameter(W) 176 else: 177 self.register_buffer("W", W) 178 179 def forward(self, x: Tensor) -> Tensor: 180 """Embed a continuous scalar input. 181 182 Args: 183 x: Scalar input tensor of shape (batch,) or (batch, 1). 184 Values are typically in [0, 1] or [0, T]. 185 186 Returns: 187 Tensor of shape (batch, embed_dim). 188 """ 189 if x.dim() == 1: 190 x = x.unsqueeze(-1) # (batch, 1) 191 projection = x * self.W.unsqueeze(0) * 2 * math.pi # (batch, embed_dim//2) 192 return torch.cat([torch.sin(projection), torch.cos(projection)], dim=-1)
Gaussian Fourier Projection for continuous scalar embedding.
A widely used technique in diffusion models and score-based generative models to embed continuous scalars (e.g. timestep t, noise level σ) into a high-dimensional space. Uses fixed random frequencies scaled by a learnable or fixed bandwidth.
Reference:
Song et al., "Score-Based Generative Modeling through Stochastic Differential Equations" https://arxiv.org/abs/2011.13456
Arguments:
- embed_dim: Output embedding dimension. Must be even.
- scale: Scale factor for the random frequencies. Controls how quickly the embedding oscillates. Typical values: 16–30.
- learnable: If True, the random weights are trainable.
Example::
gfp = GaussianFourierProjection(embed_dim=256, scale=16)
t = torch.rand(32) # continuous timesteps in [0, 1]
emb = gfp(t) # (32, 256)
# Common usage in diffusion models:
# t_emb = gfp(t)
# then feed through an MLP to condition the UNet
163 def __init__( 164 self, 165 embed_dim: int, 166 scale: float = 16.0, 167 learnable: bool = False, 168 ) -> None: 169 super().__init__() 170 if embed_dim % 2 != 0: 171 raise ValueError(f"embed_dim must be even, got {embed_dim}") 172 173 W = torch.randn(embed_dim // 2) * scale 174 if learnable: 175 self.W = nn.Parameter(W) 176 else: 177 self.register_buffer("W", W)
Initialize internal Module state, shared by both nn.Module and ScriptModule.
179 def forward(self, x: Tensor) -> Tensor: 180 """Embed a continuous scalar input. 181 182 Args: 183 x: Scalar input tensor of shape (batch,) or (batch, 1). 184 Values are typically in [0, 1] or [0, T]. 185 186 Returns: 187 Tensor of shape (batch, embed_dim). 188 """ 189 if x.dim() == 1: 190 x = x.unsqueeze(-1) # (batch, 1) 191 projection = x * self.W.unsqueeze(0) * 2 * math.pi # (batch, embed_dim//2) 192 return torch.cat([torch.sin(projection), torch.cos(projection)], dim=-1)
Embed a continuous scalar input.
Arguments:
- x: Scalar input tensor of shape (batch,) or (batch, 1). Values are typically in [0, 1] or [0, T].
Returns:
Tensor of shape (batch, embed_dim).
29class EntityEmbedding(nn.Module): 30 """Learned embedding for a single categorical feature. 31 32 Wraps ``nn.Embedding`` with automatic dimension sizing and optional 33 dropout. The auto-sizing heuristic (fast.ai rule) empirically works well 34 for tabular data without manual tuning. 35 36 Reference: 37 Howard & Gugger, "Deep Learning for Coders with fastai and PyTorch" 38 https://arxiv.org/abs/2002.04688 (entity embeddings section) 39 40 Args: 41 num_categories: Vocabulary size (number of unique categories + 1 for 42 unknown). Indices must be in [0, num_categories - 1]. 43 embed_dim: Embedding dimension. If None, uses the auto-sizing 44 heuristic: ``min(600, round(1.6 * num_categories ** 0.56))``. 45 dropout: Dropout rate applied to embeddings. 46 padding_idx: Index that will always produce a zero embedding 47 (e.g. for padding / unknown tokens). 48 49 Example:: 50 51 emb = EntityEmbedding(num_categories=50) 52 x = torch.randint(0, 50, (32,)) # batch of category indices 53 out = emb(x) # (32, embed_dim) 54 """ 55 56 def __init__( 57 self, 58 num_categories: int, 59 embed_dim: Optional[int] = None, 60 dropout: float = 0.0, 61 padding_idx: Optional[int] = None, 62 ) -> None: 63 super().__init__() 64 self.num_categories = num_categories 65 self.embed_dim = embed_dim or _auto_dim(num_categories) 66 67 self.embedding = nn.Embedding( 68 num_categories, self.embed_dim, padding_idx=padding_idx 69 ) 70 self.dropout = nn.Dropout(p=dropout) if dropout > 0 else nn.Identity() 71 72 nn.init.normal_(self.embedding.weight, std=0.01) 73 if padding_idx is not None: 74 nn.init.zeros_(self.embedding.weight[padding_idx]) 75 76 def forward(self, x: Tensor) -> Tensor: 77 """Embed categorical indices. 78 79 Args: 80 x: Long tensor of category indices, shape (...,). 81 82 Returns: 83 Float tensor of shape (..., embed_dim). 84 """ 85 return self.dropout(self.embedding(x))
Learned embedding for a single categorical feature.
Wraps nn.Embedding with automatic dimension sizing and optional
dropout. The auto-sizing heuristic (fast.ai rule) empirically works well
for tabular data without manual tuning.
Reference:
Howard & Gugger, "Deep Learning for Coders with fastai and PyTorch" https://arxiv.org/abs/2002.04688 (entity embeddings section)
Arguments:
- num_categories: Vocabulary size (number of unique categories + 1 for unknown). Indices must be in [0, num_categories - 1].
- embed_dim: Embedding dimension. If None, uses the auto-sizing
heuristic:
min(600, round(1.6 * num_categories ** 0.56)). - dropout: Dropout rate applied to embeddings.
- padding_idx: Index that will always produce a zero embedding (e.g. for padding / unknown tokens).
Example::
emb = EntityEmbedding(num_categories=50)
x = torch.randint(0, 50, (32,)) # batch of category indices
out = emb(x) # (32, embed_dim)
56 def __init__( 57 self, 58 num_categories: int, 59 embed_dim: Optional[int] = None, 60 dropout: float = 0.0, 61 padding_idx: Optional[int] = None, 62 ) -> None: 63 super().__init__() 64 self.num_categories = num_categories 65 self.embed_dim = embed_dim or _auto_dim(num_categories) 66 67 self.embedding = nn.Embedding( 68 num_categories, self.embed_dim, padding_idx=padding_idx 69 ) 70 self.dropout = nn.Dropout(p=dropout) if dropout > 0 else nn.Identity() 71 72 nn.init.normal_(self.embedding.weight, std=0.01) 73 if padding_idx is not None: 74 nn.init.zeros_(self.embedding.weight[padding_idx])
Initialize internal Module state, shared by both nn.Module and ScriptModule.
76 def forward(self, x: Tensor) -> Tensor: 77 """Embed categorical indices. 78 79 Args: 80 x: Long tensor of category indices, shape (...,). 81 82 Returns: 83 Float tensor of shape (..., embed_dim). 84 """ 85 return self.dropout(self.embedding(x))
Embed categorical indices.
Arguments:
- x: Long tensor of category indices, shape (...,).
Returns:
Float tensor of shape (..., embed_dim).
88class MultiCategoricalEmbedding(nn.Module): 89 """Joint embedding for multiple categorical columns. 90 91 Intended for tabular data where each row has several categorical features 92 (e.g. country, product category, day of week). Creates one 93 ``EntityEmbedding`` per column and concatenates the results. 94 95 Args: 96 cardinalities: Sequence of vocabulary sizes, one per categorical column. 97 E.g. ``[50, 7, 12]`` for columns with 50, 7, and 12 categories. 98 embed_dims: Per-column embedding dimensions. If None, uses the 99 auto-sizing heuristic for each column. 100 dropout: Dropout rate applied to each embedding independently. 101 padding_idx: Shared padding index applied to all columns. 102 103 Properties: 104 output_dim: Total output dimension (sum of all embed_dims). 105 106 Example:: 107 108 # 3 categorical columns: country (50 cats), weekday (7), month (12) 109 emb = MultiCategoricalEmbedding(cardinalities=[50, 7, 12]) 110 x = torch.stack([ 111 torch.randint(0, 50, (32,)), 112 torch.randint(0, 7, (32,)), 113 torch.randint(0, 12, (32,)), 114 ], dim=1) # (32, 3) 115 out = emb(x) # (32, output_dim) 116 print(emb.output_dim) 117 """ 118 119 def __init__( 120 self, 121 cardinalities: Sequence[int], 122 embed_dims: Optional[Sequence[int]] = None, 123 dropout: float = 0.0, 124 padding_idx: Optional[int] = None, 125 ) -> None: 126 super().__init__() 127 128 if embed_dims is not None and len(embed_dims) != len(cardinalities): 129 raise ValueError( 130 f"embed_dims length ({len(embed_dims)}) must match " 131 f"cardinalities length ({len(cardinalities)})" 132 ) 133 134 dims = embed_dims or [None] * len(cardinalities) # type: ignore[list-item] 135 self.embeddings = nn.ModuleList([ 136 EntityEmbedding(n, d, dropout, padding_idx) 137 for n, d in zip(cardinalities, dims) 138 ]) 139 self.output_dim: int = sum(e.embed_dim for e in self.embeddings) # type: ignore[union-attr] 140 141 def forward(self, x: Tensor) -> Tensor: 142 """Embed all categorical columns and concatenate. 143 144 Args: 145 x: Long tensor of shape (batch, num_columns) containing 146 category indices. Column order must match ``cardinalities``. 147 148 Returns: 149 Float tensor of shape (batch, output_dim). 150 """ 151 parts = [emb(x[:, i]) for i, emb in enumerate(self.embeddings)] 152 return torch.cat(parts, dim=-1) 153 154 def embedding_dims(self) -> list[tuple[int, int]]: 155 """Return list of (num_categories, embed_dim) for each column.""" 156 return [(e.num_categories, e.embed_dim) for e in self.embeddings] # type: ignore[union-attr]
Joint embedding for multiple categorical columns.
Intended for tabular data where each row has several categorical features
(e.g. country, product category, day of week). Creates one
EntityEmbedding per column and concatenates the results.
Arguments:
- cardinalities: Sequence of vocabulary sizes, one per categorical column.
E.g.
[50, 7, 12]for columns with 50, 7, and 12 categories. - embed_dims: Per-column embedding dimensions. If None, uses the auto-sizing heuristic for each column.
- dropout: Dropout rate applied to each embedding independently.
- padding_idx: Shared padding index applied to all columns.
Properties:
output_dim: Total output dimension (sum of all embed_dims).
Example::
# 3 categorical columns: country (50 cats), weekday (7), month (12)
emb = MultiCategoricalEmbedding(cardinalities=[50, 7, 12])
x = torch.stack([
torch.randint(0, 50, (32,)),
torch.randint(0, 7, (32,)),
torch.randint(0, 12, (32,)),
], dim=1) # (32, 3)
out = emb(x) # (32, output_dim)
print(emb.output_dim)
119 def __init__( 120 self, 121 cardinalities: Sequence[int], 122 embed_dims: Optional[Sequence[int]] = None, 123 dropout: float = 0.0, 124 padding_idx: Optional[int] = None, 125 ) -> None: 126 super().__init__() 127 128 if embed_dims is not None and len(embed_dims) != len(cardinalities): 129 raise ValueError( 130 f"embed_dims length ({len(embed_dims)}) must match " 131 f"cardinalities length ({len(cardinalities)})" 132 ) 133 134 dims = embed_dims or [None] * len(cardinalities) # type: ignore[list-item] 135 self.embeddings = nn.ModuleList([ 136 EntityEmbedding(n, d, dropout, padding_idx) 137 for n, d in zip(cardinalities, dims) 138 ]) 139 self.output_dim: int = sum(e.embed_dim for e in self.embeddings) # type: ignore[union-attr]
Initialize internal Module state, shared by both nn.Module and ScriptModule.
141 def forward(self, x: Tensor) -> Tensor: 142 """Embed all categorical columns and concatenate. 143 144 Args: 145 x: Long tensor of shape (batch, num_columns) containing 146 category indices. Column order must match ``cardinalities``. 147 148 Returns: 149 Float tensor of shape (batch, output_dim). 150 """ 151 parts = [emb(x[:, i]) for i, emb in enumerate(self.embeddings)] 152 return torch.cat(parts, dim=-1)
Embed all categorical columns and concatenate.
Arguments:
- x: Long tensor of shape (batch, num_columns) containing
category indices. Column order must match
cardinalities.
Returns:
Float tensor of shape (batch, output_dim).
154 def embedding_dims(self) -> list[tuple[int, int]]: 155 """Return list of (num_categories, embed_dim) for each column.""" 156 return [(e.num_categories, e.embed_dim) for e in self.embeddings] # type: ignore[union-attr]
Return list of (num_categories, embed_dim) for each column.
17class PatchEmbedding(nn.Module): 18 """Image-to-patch embedding for Vision Transformers (ViT). 19 20 Splits an image into non-overlapping patches and projects each patch 21 into a token embedding using a single convolution (equivalent to splitting 22 + linear projection, but faster). 23 24 Reference: 25 Dosovitskiy et al., "An Image is Worth 16x16 Words: Transformers for 26 Image Recognition at Scale" https://arxiv.org/abs/2010.11929 27 28 Args: 29 image_size: Input image size. Can be an int (square) or (H, W) tuple. 30 patch_size: Size of each patch. Can be an int (square) or (pH, pW) tuple. 31 in_channels: Number of input image channels. Default 3 (RGB). 32 embed_dim: Embedding dimension for each patch token. 33 bias: Whether to include bias in the projection convolution. 34 norm_layer: Optional normalization layer applied after projection. 35 E.g. ``nn.LayerNorm``. 36 flatten: If True (default), flatten the spatial patch grid into a 37 sequence. If False, return shape (B, C, H_patches, W_patches). 38 39 Properties: 40 num_patches: Total number of patches per image. 41 grid_size: (H_patches, W_patches) tuple. 42 43 Example:: 44 45 patch_emb = PatchEmbedding(image_size=224, patch_size=16, embed_dim=768) 46 images = torch.randn(4, 3, 224, 224) 47 tokens = patch_emb(images) # (4, 196, 768) 48 print(patch_emb.num_patches) # 196 49 """ 50 51 def __init__( 52 self, 53 image_size: Union[int, tuple[int, int]] = 224, 54 patch_size: Union[int, tuple[int, int]] = 16, 55 in_channels: int = 3, 56 embed_dim: int = 768, 57 bias: bool = True, 58 norm_layer: Optional[nn.Module] = None, 59 flatten: bool = True, 60 ) -> None: 61 super().__init__() 62 63 image_size = ( 64 (image_size, image_size) if isinstance(image_size, int) else image_size 65 ) 66 patch_size = ( 67 (patch_size, patch_size) if isinstance(patch_size, int) else patch_size 68 ) 69 70 if image_size[0] % patch_size[0] != 0 or image_size[1] % patch_size[1] != 0: 71 raise ValueError( 72 f"Image size {image_size} must be divisible by patch size {patch_size}" 73 ) 74 75 self.image_size = image_size 76 self.patch_size = patch_size 77 self.in_channels = in_channels 78 self.embed_dim = embed_dim 79 self.flatten = flatten 80 81 self.grid_size: tuple[int, int] = ( 82 image_size[0] // patch_size[0], 83 image_size[1] // patch_size[1], 84 ) 85 self.num_patches: int = self.grid_size[0] * self.grid_size[1] 86 87 # Conv2d is mathematically equivalent to split-then-linear, but faster 88 self.proj = nn.Conv2d( 89 in_channels, embed_dim, kernel_size=patch_size, stride=patch_size, bias=bias 90 ) 91 self.norm = norm_layer if norm_layer is not None else nn.Identity() 92 93 self._init_weights() 94 95 def _init_weights(self) -> None: 96 fan_in = self.in_channels * self.patch_size[0] * self.patch_size[1] 97 nn.init.trunc_normal_(self.proj.weight, std=math.sqrt(2.0 / fan_in)) 98 if self.proj.bias is not None: 99 nn.init.zeros_(self.proj.bias) 100 101 def forward(self, x: Tensor) -> Tensor: 102 """Project image to patch token sequence. 103 104 Args: 105 x: Image tensor of shape (B, C, H, W). 106 107 Returns: 108 If ``flatten=True`` (default): shape (B, num_patches, embed_dim). 109 If ``flatten=False``: shape (B, embed_dim, H_patches, W_patches). 110 """ 111 B, C, H, W = x.shape 112 if H != self.image_size[0] or W != self.image_size[1]: 113 raise ValueError( 114 f"Input image size ({H}x{W}) doesn't match expected " 115 f"({self.image_size[0]}x{self.image_size[1]}). " 116 "Pass a different image_size to PatchEmbedding or resize your input." 117 ) 118 119 x = self.proj(x) # (B, embed_dim, H_patches, W_patches) 120 if self.flatten: 121 x = x.flatten(2).transpose(1, 2) # (B, num_patches, embed_dim) 122 return self.norm(x)
Image-to-patch embedding for Vision Transformers (ViT).
Splits an image into non-overlapping patches and projects each patch into a token embedding using a single convolution (equivalent to splitting
- linear projection, but faster).
Reference:
Dosovitskiy et al., "An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale" https://arxiv.org/abs/2010.11929
Arguments:
- image_size: Input image size. Can be an int (square) or (H, W) tuple.
- patch_size: Size of each patch. Can be an int (square) or (pH, pW) tuple.
- in_channels: Number of input image channels. Default 3 (RGB).
- embed_dim: Embedding dimension for each patch token.
- bias: Whether to include bias in the projection convolution.
- norm_layer: Optional normalization layer applied after projection.
E.g.
nn.LayerNorm. - flatten: If True (default), flatten the spatial patch grid into a sequence. If False, return shape (B, C, H_patches, W_patches).
Properties:
num_patches: Total number of patches per image. grid_size: (H_patches, W_patches) tuple.
Example::
patch_emb = PatchEmbedding(image_size=224, patch_size=16, embed_dim=768)
images = torch.randn(4, 3, 224, 224)
tokens = patch_emb(images) # (4, 196, 768)
print(patch_emb.num_patches) # 196
51 def __init__( 52 self, 53 image_size: Union[int, tuple[int, int]] = 224, 54 patch_size: Union[int, tuple[int, int]] = 16, 55 in_channels: int = 3, 56 embed_dim: int = 768, 57 bias: bool = True, 58 norm_layer: Optional[nn.Module] = None, 59 flatten: bool = True, 60 ) -> None: 61 super().__init__() 62 63 image_size = ( 64 (image_size, image_size) if isinstance(image_size, int) else image_size 65 ) 66 patch_size = ( 67 (patch_size, patch_size) if isinstance(patch_size, int) else patch_size 68 ) 69 70 if image_size[0] % patch_size[0] != 0 or image_size[1] % patch_size[1] != 0: 71 raise ValueError( 72 f"Image size {image_size} must be divisible by patch size {patch_size}" 73 ) 74 75 self.image_size = image_size 76 self.patch_size = patch_size 77 self.in_channels = in_channels 78 self.embed_dim = embed_dim 79 self.flatten = flatten 80 81 self.grid_size: tuple[int, int] = ( 82 image_size[0] // patch_size[0], 83 image_size[1] // patch_size[1], 84 ) 85 self.num_patches: int = self.grid_size[0] * self.grid_size[1] 86 87 # Conv2d is mathematically equivalent to split-then-linear, but faster 88 self.proj = nn.Conv2d( 89 in_channels, embed_dim, kernel_size=patch_size, stride=patch_size, bias=bias 90 ) 91 self.norm = norm_layer if norm_layer is not None else nn.Identity() 92 93 self._init_weights()
Initialize internal Module state, shared by both nn.Module and ScriptModule.
101 def forward(self, x: Tensor) -> Tensor: 102 """Project image to patch token sequence. 103 104 Args: 105 x: Image tensor of shape (B, C, H, W). 106 107 Returns: 108 If ``flatten=True`` (default): shape (B, num_patches, embed_dim). 109 If ``flatten=False``: shape (B, embed_dim, H_patches, W_patches). 110 """ 111 B, C, H, W = x.shape 112 if H != self.image_size[0] or W != self.image_size[1]: 113 raise ValueError( 114 f"Input image size ({H}x{W}) doesn't match expected " 115 f"({self.image_size[0]}x{self.image_size[1]}). " 116 "Pass a different image_size to PatchEmbedding or resize your input." 117 ) 118 119 x = self.proj(x) # (B, embed_dim, H_patches, W_patches) 120 if self.flatten: 121 x = x.flatten(2).transpose(1, 2) # (B, num_patches, embed_dim) 122 return self.norm(x)
Project image to patch token sequence.
Arguments:
- x: Image tensor of shape (B, C, H, W).
Returns:
If
flatten=True(default): shape (B, num_patches, embed_dim). Ifflatten=False: shape (B, embed_dim, H_patches, W_patches).
125class TubeletEmbedding(nn.Module): 126 """Spatiotemporal tubelet embedding for video transformers. 127 128 Extends patch embedding to video by extracting non-overlapping 3D tubelets 129 (short video clips over a patch region) using a single Conv3d. Each tubelet 130 becomes one token. 131 132 Used in: VideoMAE, ViViT, TimeSformer variants. 133 134 Reference: 135 Tong et al., "VideoMAE: Masked Autoencoders are Data-Efficient Learners 136 for Self-Supervised Video Pre-Training" https://arxiv.org/abs/2203.12602 137 138 Args: 139 image_size: Spatial frame size. Int or (H, W). 140 patch_size: Spatial patch size. Int or (pH, pW). 141 tubelet_size: Number of frames per tubelet (temporal stride). 142 in_channels: Input channels (default 3 for RGB video). 143 embed_dim: Output embedding dimension. 144 bias: Whether to include bias in the projection. 145 flatten: If True, return shape (B, num_tubelets, embed_dim). 146 147 Properties: 148 num_patches_per_frame: Spatial patches per frame. 149 num_tubelets_per_video: Total tubelets for a given number of frames. 150 151 Example:: 152 153 tubelet_emb = TubeletEmbedding( 154 image_size=224, patch_size=16, tubelet_size=2, embed_dim=768 155 ) 156 video = torch.randn(2, 3, 16, 224, 224) # (B, C, T, H, W) 157 tokens = tubelet_emb(video) # (2, 1568, 768) 158 # 1568 = (16/2) * (224/16) * (224/16) = 8 * 14 * 14 159 """ 160 161 def __init__( 162 self, 163 image_size: Union[int, tuple[int, int]] = 224, 164 patch_size: Union[int, tuple[int, int]] = 16, 165 tubelet_size: int = 2, 166 in_channels: int = 3, 167 embed_dim: int = 768, 168 bias: bool = True, 169 norm_layer: Optional[nn.Module] = None, 170 flatten: bool = True, 171 ) -> None: 172 super().__init__() 173 174 image_size = ( 175 (image_size, image_size) if isinstance(image_size, int) else image_size 176 ) 177 patch_size = ( 178 (patch_size, patch_size) if isinstance(patch_size, int) else patch_size 179 ) 180 181 self.image_size = image_size 182 self.patch_size = patch_size 183 self.tubelet_size = tubelet_size 184 self.in_channels = in_channels 185 self.embed_dim = embed_dim 186 self.flatten = flatten 187 188 self.num_patches_per_frame: int = ( 189 (image_size[0] // patch_size[0]) * (image_size[1] // patch_size[1]) 190 ) 191 192 self.proj = nn.Conv3d( 193 in_channels, 194 embed_dim, 195 kernel_size=(tubelet_size, *patch_size), 196 stride=(tubelet_size, *patch_size), 197 bias=bias, 198 ) 199 self.norm = norm_layer if norm_layer is not None else nn.Identity() 200 self._init_weights() 201 202 def _init_weights(self) -> None: 203 fan_in = ( 204 self.in_channels 205 * self.tubelet_size 206 * self.patch_size[0] 207 * self.patch_size[1] 208 ) 209 nn.init.trunc_normal_(self.proj.weight, std=math.sqrt(2.0 / fan_in)) 210 if self.proj.bias is not None: 211 nn.init.zeros_(self.proj.bias) 212 213 def forward(self, x: Tensor) -> Tensor: 214 """Project video to tubelet token sequence. 215 216 Args: 217 x: Video tensor of shape (B, C, T, H, W). 218 219 Returns: 220 If ``flatten=True``: shape (B, num_tubelets, embed_dim). 221 If ``flatten=False``: shape (B, embed_dim, T//tubelet_size, H//pH, W//pW). 222 """ 223 x = self.proj(x) # (B, embed_dim, T', H', W') 224 if self.flatten: 225 B, C, T, H, W = x.shape 226 x = x.flatten(2).transpose(1, 2) # (B, T*H*W, embed_dim) 227 return self.norm(x)
Spatiotemporal tubelet embedding for video transformers.
Extends patch embedding to video by extracting non-overlapping 3D tubelets (short video clips over a patch region) using a single Conv3d. Each tubelet becomes one token.
Used in: VideoMAE, ViViT, TimeSformer variants.
Reference:
Tong et al., "VideoMAE: Masked Autoencoders are Data-Efficient Learners for Self-Supervised Video Pre-Training" https://arxiv.org/abs/2203.12602
Arguments:
- image_size: Spatial frame size. Int or (H, W).
- patch_size: Spatial patch size. Int or (pH, pW).
- tubelet_size: Number of frames per tubelet (temporal stride).
- in_channels: Input channels (default 3 for RGB video).
- embed_dim: Output embedding dimension.
- bias: Whether to include bias in the projection.
- flatten: If True, return shape (B, num_tubelets, embed_dim).
Properties:
num_patches_per_frame: Spatial patches per frame. num_tubelets_per_video: Total tubelets for a given number of frames.
Example::
tubelet_emb = TubeletEmbedding(
image_size=224, patch_size=16, tubelet_size=2, embed_dim=768
)
video = torch.randn(2, 3, 16, 224, 224) # (B, C, T, H, W)
tokens = tubelet_emb(video) # (2, 1568, 768)
# 1568 = (16/2) * (224/16) * (224/16) = 8 * 14 * 14
161 def __init__( 162 self, 163 image_size: Union[int, tuple[int, int]] = 224, 164 patch_size: Union[int, tuple[int, int]] = 16, 165 tubelet_size: int = 2, 166 in_channels: int = 3, 167 embed_dim: int = 768, 168 bias: bool = True, 169 norm_layer: Optional[nn.Module] = None, 170 flatten: bool = True, 171 ) -> None: 172 super().__init__() 173 174 image_size = ( 175 (image_size, image_size) if isinstance(image_size, int) else image_size 176 ) 177 patch_size = ( 178 (patch_size, patch_size) if isinstance(patch_size, int) else patch_size 179 ) 180 181 self.image_size = image_size 182 self.patch_size = patch_size 183 self.tubelet_size = tubelet_size 184 self.in_channels = in_channels 185 self.embed_dim = embed_dim 186 self.flatten = flatten 187 188 self.num_patches_per_frame: int = ( 189 (image_size[0] // patch_size[0]) * (image_size[1] // patch_size[1]) 190 ) 191 192 self.proj = nn.Conv3d( 193 in_channels, 194 embed_dim, 195 kernel_size=(tubelet_size, *patch_size), 196 stride=(tubelet_size, *patch_size), 197 bias=bias, 198 ) 199 self.norm = norm_layer if norm_layer is not None else nn.Identity() 200 self._init_weights()
Initialize internal Module state, shared by both nn.Module and ScriptModule.
213 def forward(self, x: Tensor) -> Tensor: 214 """Project video to tubelet token sequence. 215 216 Args: 217 x: Video tensor of shape (B, C, T, H, W). 218 219 Returns: 220 If ``flatten=True``: shape (B, num_tubelets, embed_dim). 221 If ``flatten=False``: shape (B, embed_dim, T//tubelet_size, H//pH, W//pW). 222 """ 223 x = self.proj(x) # (B, embed_dim, T', H', W') 224 if self.flatten: 225 B, C, T, H, W = x.shape 226 x = x.flatten(2).transpose(1, 2) # (B, T*H*W, embed_dim) 227 return self.norm(x)
Project video to tubelet token sequence.
Arguments:
- x: Video tensor of shape (B, C, T, H, W).
Returns:
If
flatten=True: shape (B, num_tubelets, embed_dim). Ifflatten=False: shape (B, embed_dim, T//tubelet_size, H//pH, W//pW).
18class CyclicEmbedding(nn.Module): 19 """Cyclic encoding for periodic scalar features. 20 21 Encodes a scalar that cycles over a known period (e.g. hour of day, 22 day of week, month of year) as (sin, cos) pairs. This preserves the 23 topology of the cycle — 11pm and 1am are close, not far apart. 24 25 This is a fixed, non-learned transformation that produces a 2D output 26 per input feature. Often stacked with other embeddings. 27 28 Args: 29 period: The period of the cycle. E.g. 24 for hours, 7 for days, 30 12 for months, 60 for seconds/minutes. 31 normalize_input: If True, input is assumed to be already in [0, period). 32 If False, the raw value is used directly. Default True. 33 34 Example:: 35 36 hour_emb = CyclicEmbedding(period=24) 37 hours = torch.tensor([0.0, 6.0, 12.0, 18.0, 23.0]) 38 out = hour_emb(hours) # (5, 2) 39 40 # Combine multiple cyclic features 41 hour_enc = CyclicEmbedding(24)(hour_of_day) # (B, 2) 42 dow_enc = CyclicEmbedding(7)(day_of_week) # (B, 2) 43 month_enc = CyclicEmbedding(12)(month) # (B, 2) 44 combined = torch.cat([hour_enc, dow_enc, month_enc], dim=-1) # (B, 6) 45 """ 46 47 def __init__(self, period: float, normalize_input: bool = True) -> None: 48 super().__init__() 49 self.period = period 50 self.normalize_input = normalize_input 51 52 def forward(self, x: Tensor) -> Tensor: 53 """Encode cyclic scalar as (sin, cos) pair. 54 55 Args: 56 x: Scalar tensor of shape (...,) with values in [0, period). 57 58 Returns: 59 Tensor of shape (..., 2) with [sin, cos] encoding. 60 """ 61 angle = (2 * math.pi * x) / self.period 62 return torch.stack([torch.sin(angle), torch.cos(angle)], dim=-1)
Cyclic encoding for periodic scalar features.
Encodes a scalar that cycles over a known period (e.g. hour of day, day of week, month of year) as (sin, cos) pairs. This preserves the topology of the cycle — 11pm and 1am are close, not far apart.
This is a fixed, non-learned transformation that produces a 2D output per input feature. Often stacked with other embeddings.
Arguments:
- period: The period of the cycle. E.g. 24 for hours, 7 for days, 12 for months, 60 for seconds/minutes.
- normalize_input: If True, input is assumed to be already in [0, period). If False, the raw value is used directly. Default True.
Example::
hour_emb = CyclicEmbedding(period=24)
hours = torch.tensor([0.0, 6.0, 12.0, 18.0, 23.0])
out = hour_emb(hours) # (5, 2)
# Combine multiple cyclic features
hour_enc = CyclicEmbedding(24)(hour_of_day) # (B, 2)
dow_enc = CyclicEmbedding(7)(day_of_week) # (B, 2)
month_enc = CyclicEmbedding(12)(month) # (B, 2)
combined = torch.cat([hour_enc, dow_enc, month_enc], dim=-1) # (B, 6)
47 def __init__(self, period: float, normalize_input: bool = True) -> None: 48 super().__init__() 49 self.period = period 50 self.normalize_input = normalize_input
Initialize internal Module state, shared by both nn.Module and ScriptModule.
52 def forward(self, x: Tensor) -> Tensor: 53 """Encode cyclic scalar as (sin, cos) pair. 54 55 Args: 56 x: Scalar tensor of shape (...,) with values in [0, period). 57 58 Returns: 59 Tensor of shape (..., 2) with [sin, cos] encoding. 60 """ 61 angle = (2 * math.pi * x) / self.period 62 return torch.stack([torch.sin(angle), torch.cos(angle)], dim=-1)
Encode cyclic scalar as (sin, cos) pair.
Arguments:
- x: Scalar tensor of shape (...,) with values in [0, period).
Returns:
Tensor of shape (..., 2) with [sin, cos] encoding.
65class TimestampEmbedding(nn.Module): 66 """Embedding for raw continuous timestamps or datetime feature vectors. 67 68 Two modes: 69 70 1. **Scalar mode**: Takes a raw scalar timestamp (e.g. Unix time, 71 normalized time in [0, 1]) and produces an embedding using 72 a Gaussian Fourier projection followed by an MLP. 73 74 2. **Feature mode**: Takes pre-extracted calendar features (hour, day, 75 month, etc.) as a vector and projects them with cyclic encodings + MLP. 76 77 For most practical cases, scalar mode is simpler and works well. 78 79 Args: 80 embed_dim: Output embedding dimension. 81 num_frequencies: Number of Fourier frequency components. 82 scale: Frequency scale for the Fourier projection. 83 mlp_layers: Number of MLP layers after the Fourier projection. 84 85 Example:: 86 87 ts_emb = TimestampEmbedding(embed_dim=64) 88 # Normalized timestamps in [0, 1] 89 t = torch.rand(32) 90 emb = ts_emb(t) # (32, 64) 91 """ 92 93 def __init__( 94 self, 95 embed_dim: int, 96 num_frequencies: int = 64, 97 scale: float = 10.0, 98 mlp_layers: int = 2, 99 ) -> None: 100 super().__init__() 101 if embed_dim % 2 != 0: 102 raise ValueError(f"embed_dim must be even, got {embed_dim}") 103 104 # Random Fourier projection (fixed) 105 W = torch.randn(num_frequencies) * scale 106 self.register_buffer("W", W) 107 self.num_frequencies = num_frequencies 108 109 # MLP: Fourier features → embed_dim 110 fourier_dim = 2 * num_frequencies 111 layers = [] 112 in_dim = fourier_dim 113 for _ in range(mlp_layers - 1): 114 layers += [nn.Linear(in_dim, embed_dim), nn.SiLU()] 115 in_dim = embed_dim 116 layers.append(nn.Linear(in_dim, embed_dim)) 117 self.mlp = nn.Sequential(*layers) 118 119 self._init_weights() 120 121 def _init_weights(self) -> None: 122 for m in self.mlp.modules(): 123 if isinstance(m, nn.Linear): 124 nn.init.xavier_uniform_(m.weight) 125 nn.init.zeros_(m.bias) 126 127 def forward(self, t: Tensor) -> Tensor: 128 """Embed a continuous timestamp. 129 130 Args: 131 t: Scalar timestamps of shape (batch,) or (batch, 1). 132 Works best when normalized to a consistent range. 133 134 Returns: 135 Tensor of shape (batch, embed_dim). 136 """ 137 if t.dim() == 1: 138 t = t.unsqueeze(-1) # (batch, 1) 139 proj = t * self.W.unsqueeze(0) * 2 * math.pi # (batch, num_freq) 140 fourier = torch.cat([torch.sin(proj), torch.cos(proj)], dim=-1) 141 return self.mlp(fourier)
Embedding for raw continuous timestamps or datetime feature vectors.
Two modes:
Scalar mode: Takes a raw scalar timestamp (e.g. Unix time, normalized time in [0, 1]) and produces an embedding using a Gaussian Fourier projection followed by an MLP.
Feature mode: Takes pre-extracted calendar features (hour, day, month, etc.) as a vector and projects them with cyclic encodings + MLP.
For most practical cases, scalar mode is simpler and works well.
Arguments:
- embed_dim: Output embedding dimension.
- num_frequencies: Number of Fourier frequency components.
- scale: Frequency scale for the Fourier projection.
- mlp_layers: Number of MLP layers after the Fourier projection.
Example::
ts_emb = TimestampEmbedding(embed_dim=64)
# Normalized timestamps in [0, 1]
t = torch.rand(32)
emb = ts_emb(t) # (32, 64)
93 def __init__( 94 self, 95 embed_dim: int, 96 num_frequencies: int = 64, 97 scale: float = 10.0, 98 mlp_layers: int = 2, 99 ) -> None: 100 super().__init__() 101 if embed_dim % 2 != 0: 102 raise ValueError(f"embed_dim must be even, got {embed_dim}") 103 104 # Random Fourier projection (fixed) 105 W = torch.randn(num_frequencies) * scale 106 self.register_buffer("W", W) 107 self.num_frequencies = num_frequencies 108 109 # MLP: Fourier features → embed_dim 110 fourier_dim = 2 * num_frequencies 111 layers = [] 112 in_dim = fourier_dim 113 for _ in range(mlp_layers - 1): 114 layers += [nn.Linear(in_dim, embed_dim), nn.SiLU()] 115 in_dim = embed_dim 116 layers.append(nn.Linear(in_dim, embed_dim)) 117 self.mlp = nn.Sequential(*layers) 118 119 self._init_weights()
Initialize internal Module state, shared by both nn.Module and ScriptModule.
127 def forward(self, t: Tensor) -> Tensor: 128 """Embed a continuous timestamp. 129 130 Args: 131 t: Scalar timestamps of shape (batch,) or (batch, 1). 132 Works best when normalized to a consistent range. 133 134 Returns: 135 Tensor of shape (batch, embed_dim). 136 """ 137 if t.dim() == 1: 138 t = t.unsqueeze(-1) # (batch, 1) 139 proj = t * self.W.unsqueeze(0) * 2 * math.pi # (batch, num_freq) 140 fourier = torch.cat([torch.sin(proj), torch.cos(proj)], dim=-1) 141 return self.mlp(fourier)
Embed a continuous timestamp.
Arguments:
- t: Scalar timestamps of shape (batch,) or (batch, 1). Works best when normalized to a consistent range.
Returns:
Tensor of shape (batch, embed_dim).
144class FrequencyEmbedding(nn.Module): 145 """Learnable frequency decomposition for periodic time series. 146 147 Decomposes input time values into a bank of learnable sinusoidal 148 oscillators. Each oscillator has a learnable frequency, phase, and 149 amplitude. The output is a rich, differentiable representation of 150 the temporal structure. 151 152 Well-suited for: forecasting models, time series classification, 153 and any model that needs to discover periodic structure automatically. 154 155 Reference: 156 Inspired by Neural Basis Expansion Analysis (N-BEATS) and 157 Time2Vec (Kazemi et al., 2019) https://arxiv.org/abs/1907.05321 158 159 Args: 160 embed_dim: Number of sinusoidal components. Output dimension is 161 ``embed_dim + 1`` (one linear trend term is always included). 162 learnable_freq: If True, frequencies are learnable. If False, 163 uses log-spaced fixed frequencies (like a Fourier basis). 164 165 Example:: 166 167 freq_emb = FrequencyEmbedding(embed_dim=32) 168 t = torch.linspace(0, 1, 100).unsqueeze(0) # (1, 100) time steps 169 out = freq_emb(t) # (1, 100, 33) 170 """ 171 172 def __init__(self, embed_dim: int, learnable_freq: bool = True) -> None: 173 super().__init__() 174 self.embed_dim = embed_dim 175 176 if learnable_freq: 177 self.freq = nn.Parameter(torch.randn(embed_dim)) 178 self.phase = nn.Parameter(torch.zeros(embed_dim)) 179 else: 180 # Log-spaced frequencies covering multiple time scales 181 freq = torch.exp( 182 torch.linspace(0, math.log(embed_dim), embed_dim) 183 ) 184 self.register_buffer("freq", freq) 185 self.register_buffer("phase", torch.zeros(embed_dim)) 186 187 self.amp = nn.Parameter(torch.ones(embed_dim + 1)) 188 self.bias = nn.Parameter(torch.zeros(embed_dim + 1)) 189 190 def forward(self, t: Tensor) -> Tensor: 191 """Compute frequency embedding for time inputs. 192 193 Args: 194 t: Time tensor of shape (batch, seq_len) or (batch,). 195 196 Returns: 197 Tensor of shape (..., embed_dim + 1) where the last dimension 198 contains sinusoidal components plus one linear trend component. 199 """ 200 # t: (...) → (..., 1) 201 t = t.unsqueeze(-1) 202 203 # Linear trend component 204 linear = t # (..., 1) 205 206 # Sinusoidal components 207 angles = t * self.freq + self.phase # (..., embed_dim) 208 periodic = torch.sin(angles) 209 210 # Concatenate and scale 211 out = torch.cat([linear, periodic], dim=-1) # (..., embed_dim + 1) 212 return self.amp * out + self.bias
Learnable frequency decomposition for periodic time series.
Decomposes input time values into a bank of learnable sinusoidal oscillators. Each oscillator has a learnable frequency, phase, and amplitude. The output is a rich, differentiable representation of the temporal structure.
Well-suited for: forecasting models, time series classification, and any model that needs to discover periodic structure automatically.
Reference:
Inspired by Neural Basis Expansion Analysis (N-BEATS) and Time2Vec (Kazemi et al., 2019) https://arxiv.org/abs/1907.05321
Arguments:
- embed_dim: Number of sinusoidal components. Output dimension is
embed_dim + 1(one linear trend term is always included). - learnable_freq: If True, frequencies are learnable. If False, uses log-spaced fixed frequencies (like a Fourier basis).
Example::
freq_emb = FrequencyEmbedding(embed_dim=32)
t = torch.linspace(0, 1, 100).unsqueeze(0) # (1, 100) time steps
out = freq_emb(t) # (1, 100, 33)
172 def __init__(self, embed_dim: int, learnable_freq: bool = True) -> None: 173 super().__init__() 174 self.embed_dim = embed_dim 175 176 if learnable_freq: 177 self.freq = nn.Parameter(torch.randn(embed_dim)) 178 self.phase = nn.Parameter(torch.zeros(embed_dim)) 179 else: 180 # Log-spaced frequencies covering multiple time scales 181 freq = torch.exp( 182 torch.linspace(0, math.log(embed_dim), embed_dim) 183 ) 184 self.register_buffer("freq", freq) 185 self.register_buffer("phase", torch.zeros(embed_dim)) 186 187 self.amp = nn.Parameter(torch.ones(embed_dim + 1)) 188 self.bias = nn.Parameter(torch.zeros(embed_dim + 1))
Initialize internal Module state, shared by both nn.Module and ScriptModule.
190 def forward(self, t: Tensor) -> Tensor: 191 """Compute frequency embedding for time inputs. 192 193 Args: 194 t: Time tensor of shape (batch, seq_len) or (batch,). 195 196 Returns: 197 Tensor of shape (..., embed_dim + 1) where the last dimension 198 contains sinusoidal components plus one linear trend component. 199 """ 200 # t: (...) → (..., 1) 201 t = t.unsqueeze(-1) 202 203 # Linear trend component 204 linear = t # (..., 1) 205 206 # Sinusoidal components 207 angles = t * self.freq + self.phase # (..., embed_dim) 208 periodic = torch.sin(angles) 209 210 # Concatenate and scale 211 out = torch.cat([linear, periodic], dim=-1) # (..., embed_dim + 1) 212 return self.amp * out + self.bias
Compute frequency embedding for time inputs.
Arguments:
- t: Time tensor of shape (batch, seq_len) or (batch,).
Returns:
Tensor of shape (..., embed_dim + 1) where the last dimension contains sinusoidal components plus one linear trend component.