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

 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]
class RotaryEmbedding(torch.nn.modules.module.Module):
 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)
RotaryEmbedding( dim: int, max_seq_len: int = 2048, base: int = 10000, use_fused: bool = False, device: Optional[torch.device] = None)
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.

dim
max_seq_len
base
use_fused
def forward( self, q: torch.Tensor, k: torch.Tensor, seq_dim: int = -2) -> tuple[torch.Tensor, torch.Tensor]:
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.

class ALiBiEmbedding(torch.nn.modules.module.Module):
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)
ALiBiEmbedding(num_heads: int, max_seq_len: int = 2048)
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.

num_heads
def forward(self, attn_scores: torch.Tensor) -> torch.Tensor:
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.

class SinusoidalEmbedding(torch.nn.modules.module.Module):
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)
SinusoidalEmbedding( dim: int, max_seq_len: int = 4096, dropout: float = 0.0, learned_scale: bool = False)
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.

dim
dropout
scale
def forward(self, x: torch.Tensor) -> torch.Tensor:
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.

class LearnedPositionalEmbedding(torch.nn.modules.module.Module):
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)
LearnedPositionalEmbedding( max_seq_len: int, dim: int, dropout: float = 0.0, padding_idx: Optional[int] = None)
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.

embedding
dropout
def forward(self, x: torch.Tensor, offset: int = 0) -> torch.Tensor:
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.

class RandomFourierFeatures(torch.nn.modules.module.Module):
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)
RandomFourierFeatures( in_features: int, out_features: int, sigma: float = 1.0, trainable: bool = False)
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.

in_features
out_features
sigma
def forward(self, x: torch.Tensor) -> torch.Tensor:
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).

class LearnedFourierFeatures(torch.nn.modules.module.Module):
 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)
LearnedFourierFeatures( in_features: int, num_frequencies: int, out_features: int, sigma_init: float = 1.0)
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.

freq
proj
def forward(self, x: torch.Tensor) -> torch.Tensor:
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).

class GaussianFourierProjection(torch.nn.modules.module.Module):
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
GaussianFourierProjection(embed_dim: int, scale: float = 16.0, learnable: bool = False)
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.

def forward(self, x: torch.Tensor) -> torch.Tensor:
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).

class EntityEmbedding(torch.nn.modules.module.Module):
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)
EntityEmbedding( num_categories: int, embed_dim: Optional[int] = None, dropout: float = 0.0, padding_idx: Optional[int] = None)
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.

num_categories
embed_dim
embedding
dropout
def forward(self, x: torch.Tensor) -> torch.Tensor:
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).

class MultiCategoricalEmbedding(torch.nn.modules.module.Module):
 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)
MultiCategoricalEmbedding( cardinalities: Sequence[int], embed_dims: Optional[Sequence[int]] = None, dropout: float = 0.0, padding_idx: Optional[int] = None)
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.

embeddings
output_dim: int
def forward(self, x: torch.Tensor) -> torch.Tensor:
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).

def embedding_dims(self) -> list[tuple[int, int]]:
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.

class PatchEmbedding(torch.nn.modules.module.Module):
 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
PatchEmbedding( image_size: Union[int, tuple[int, int]] = 224, patch_size: Union[int, tuple[int, int]] = 16, in_channels: int = 3, embed_dim: int = 768, bias: bool = True, norm_layer: Optional[torch.nn.modules.module.Module] = None, flatten: bool = True)
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.

image_size
patch_size
in_channels
embed_dim
flatten
grid_size: tuple[int, int]
num_patches: int
proj
norm
def forward(self, x: torch.Tensor) -> torch.Tensor:
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). If flatten=False: shape (B, embed_dim, H_patches, W_patches).

class TubeletEmbedding(torch.nn.modules.module.Module):
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
TubeletEmbedding( image_size: Union[int, tuple[int, int]] = 224, patch_size: Union[int, tuple[int, int]] = 16, tubelet_size: int = 2, in_channels: int = 3, embed_dim: int = 768, bias: bool = True, norm_layer: Optional[torch.nn.modules.module.Module] = None, flatten: bool = True)
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.

image_size
patch_size
tubelet_size
in_channels
embed_dim
flatten
num_patches_per_frame: int
proj
norm
def forward(self, x: torch.Tensor) -> torch.Tensor:
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). If flatten=False: shape (B, embed_dim, T//tubelet_size, H//pH, W//pW).

class CyclicEmbedding(torch.nn.modules.module.Module):
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)
CyclicEmbedding(period: float, normalize_input: bool = True)
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.

period
normalize_input
def forward(self, x: torch.Tensor) -> torch.Tensor:
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.

class TimestampEmbedding(torch.nn.modules.module.Module):
 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:

  1. 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.

  2. 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)
TimestampEmbedding( embed_dim: int, num_frequencies: int = 64, scale: float = 10.0, mlp_layers: int = 2)
 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.

num_frequencies
mlp
def forward(self, t: torch.Tensor) -> torch.Tensor:
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).

class FrequencyEmbedding(torch.nn.modules.module.Module):
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)
FrequencyEmbedding(embed_dim: int, learnable_freq: bool = True)
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.

embed_dim
amp
bias
def forward(self, t: torch.Tensor) -> torch.Tensor:
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.