Skip to content
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
930f855
[ENH] Units_v2 Model added
Muhammad-Rebaal Mar 9, 2026
0f4faf4
Merge branch 'main' into units_v2
Muhammad-Rebaal Mar 10, 2026
43dd89d
Merge branch 'main' into units_v2
Muhammad-Rebaal Mar 16, 2026
2d46f32
Add UniTS model, its package container, and integrate it into the mod…
Muhammad-Rebaal Mar 16, 2026
e9cd22f
[ENH] Added a default fixture
Muhammad-Rebaal Mar 17, 2026
c8c81a9
feat: Add `UniTS_pkg_v2` for UniTS model definition, metadata, and te…
Muhammad-Rebaal Mar 17, 2026
61e4d29
[BUG] Fix default fixture context_length and shared dict
Muhammad-Rebaal Mar 17, 2026
ee65d40
Merge branch 'main' into units_v2
Muhammad-Rebaal Mar 21, 2026
7e13f3b
Merge branch 'main' into units_v2
Muhammad-Rebaal Mar 23, 2026
8900902
fix: Code Refactored
Muhammad-Rebaal Mar 23, 2026
5701122
Merge branch 'main' into units_v2
Muhammad-Rebaal Mar 25, 2026
4152064
Merge branch 'main' into units_v2
Muhammad-Rebaal Apr 8, 2026
9efb9df
Merge branch 'main' into units_v2
Muhammad-Rebaal May 11, 2026
e17fb97
Merge branch 'main' into units_v2
Muhammad-Rebaal Jun 8, 2026
78b7bd2
Merge branch 'main' into units_v2
Muhammad-Rebaal Jun 15, 2026
a7c0172
feat: add UniTS_pkg_v2 remaining tags and update the import
Muhammad-Rebaal Jun 15, 2026
08f46b0
fix : units_v2 test error
Muhammad-Rebaal Jun 16, 2026
49c27f8
fix: pytest error
Muhammad-Rebaal Jun 16, 2026
349c726
fix: pytest error
Muhammad-Rebaal Jun 16, 2026
9f192ed
Merge branch 'main' into units_v2
Muhammad-Rebaal Jun 19, 2026
24f57c9
Merge branch 'main' into units_v2
Muhammad-Rebaal Jun 21, 2026
858a146
fix:pytest hallucination
Muhammad-Rebaal Jun 22, 2026
bc89293
Merge branch 'main' into units_v2
Muhammad-Rebaal Jun 25, 2026
18afcbb
Merge branch 'units_v2' of https://github.com/Muhammad-Rebaal/pytorch…
Muhammad-Rebaal Jun 27, 2026
4327f59
Merge branch 'main' into units_v2
Muhammad-Rebaal Jun 29, 2026
1fa26b3
fix: removed the v1 tags
Muhammad-Rebaal Jun 29, 2026
8e7919f
fix: Performed De-duplication
Muhammad-Rebaal Jul 3, 2026
7eb7fce
Added API reference for the Units model
Muhammad-Rebaal Jul 3, 2026
0dc7a8d
feat: Converted the model on the BaseClass instead of the TslibModel …
Muhammad-Rebaal Jul 6, 2026
26e8290
Merge branch 'main' into units_v2
Muhammad-Rebaal Jul 14, 2026
013bd15
Merge branch 'main' into units_v2
Muhammad-Rebaal Jul 26, 2026
96c229e
remove the non-important tag, losses suport added, and tests added
Muhammad-Rebaal Jul 27, 2026
36f2e6a
fix pytest error
Muhammad-Rebaal Jul 27, 2026
dccfe8c
fix code quality
Muhammad-Rebaal Jul 27, 2026
3c3b662
fix: tag removed and renamed param name
Muhammad-Rebaal Jul 30, 2026
e069a29
remove dist loss as there isn't any implementation in base_model_v2
Muhammad-Rebaal Jul 31, 2026
874e477
Merge branch 'main' of https://github.com/Muhammad-Rebaal/pytorch-for…
Muhammad-Rebaal Aug 3, 2026
0de97fc
fix : Code Quality
Muhammad-Rebaal Aug 3, 2026
038d8c4
fix Code Quality
Muhammad-Rebaal Aug 3, 2026
30b0aab
fix : Remove dead import
Muhammad-Rebaal Aug 8, 2026
aca98b1
Merge branch 'main' into units_v2
Muhammad-Rebaal Aug 8, 2026
81fc45a
Merge branch 'main' into units_v2
Muhammad-Rebaal Aug 9, 2026
b05ab55
Merge branch 'main' of https://github.com/Muhammad-Rebaal/pytorch-for…
Muhammad-Rebaal Aug 12, 2026
f00be60
fix: Removed the Distribution loss and add Todo for that
Muhammad-Rebaal Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/m_layer_v2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ See the detailed API documentation for the V2 base classes and specific model im
models.samformer._samformer_v2.Samformer
models.tide._tide_dsipts._tide_v2.TIDE
models.timexer._timexer_v2.TimeXer
models.units._units_v2.UniTS
1 change: 1 addition & 0 deletions docs/source/pkg_v2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,4 @@ See the detailed API documentation for the available V2 Package classes below:
models.samformer._samformer_v2_pkg.Samformer_pkg_v2
models.tide._tide_dsipts._tide_v2_pkg.TIDE_pkg_v2
models.timexer._timexer_pkg_v2.TimeXer_pkg_v2
models.units._units_pkg_v2.UniTS_pkg_v2
7 changes: 6 additions & 1 deletion pytorch_forecasting/layers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
FullAttention,
TriangularCausalMask,
)
from pytorch_forecasting.layers._blocks import ResidualBlock
from pytorch_forecasting.layers._blocks import ResidualBlock, _TransformerBlock
from pytorch_forecasting.layers._decomposition import SeriesDecomposition
from pytorch_forecasting.layers._embeddings import (
DataEmbedding_inverted,
EnEmbedding,
PositionalEmbedding,
_PatchEmbedding,
_PositionalEmbedding,
embedding_cat_variables,
)
from pytorch_forecasting.layers._encoders import (
Expand Down Expand Up @@ -41,6 +43,9 @@
"DataEmbedding_inverted",
"EnEmbedding",
"PositionalEmbedding",
"_PatchEmbedding",
"_PositionalEmbedding",
"_TransformerBlock",
"Encoder",
"EncoderLayer",
"FlattenHead",
Expand Down
3 changes: 2 additions & 1 deletion pytorch_forecasting/layers/_blocks/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from pytorch_forecasting.layers._blocks._residual_block_dsipts import ResidualBlock
from pytorch_forecasting.layers._blocks._transformer_block import _TransformerBlock

__all__ = ["ResidualBlock"]
__all__ = ["ResidualBlock", "_TransformerBlock"]
45 changes: 45 additions & 0 deletions pytorch_forecasting/layers/_blocks/_transformer_block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""
Pre-norm Transformer Encoder Block for PTF.
"""

import torch
import torch.nn as nn


class _TransformerBlock(nn.Module):
"""
Pre-norm transformer encoder block (MHSA + FFN).

Parameters
----------
d_model : int
Model dimension.
n_heads : int
Number of attention heads.
d_ff : int
Feed-forward hidden dimension.
dropout : float
Dropout probability.
"""

def __init__(self, d_model: int, n_heads: int, d_ff: int, dropout: float = 0.1):
super().__init__()
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.attn = nn.MultiheadAttention(
embed_dim=d_model, num_heads=n_heads, dropout=dropout, batch_first=True
)
self.ff = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(d_ff, d_model),
nn.Dropout(dropout),
)

def forward(self, x: torch.Tensor) -> torch.Tensor:
normed = self.norm1(x)
attn_out, _ = self.attn(normed, normed, normed)
x = x + attn_out
x = x + self.ff(self.norm2(x))
return x
4 changes: 4 additions & 0 deletions pytorch_forecasting/layers/_embeddings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
DataEmbedding_inverted,
)
from pytorch_forecasting.layers._embeddings._en_embedding import EnEmbedding
from pytorch_forecasting.layers._embeddings._patch_embedding import _PatchEmbedding
from pytorch_forecasting.layers._embeddings._positional_embedding import (
PositionalEmbedding,
_PositionalEmbedding,
)
from pytorch_forecasting.layers._embeddings._sub_nn import embedding_cat_variables

__all__ = [
"PositionalEmbedding",
"_PositionalEmbedding",
"_PatchEmbedding",
"DataEmbedding_inverted",
"EnEmbedding",
"embedding_cat_variables",
Expand Down
55 changes: 55 additions & 0 deletions pytorch_forecasting/layers/_embeddings/_patch_embedding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Patch Embedding Layer for PTF.
"""

import torch
import torch.nn as nn


class _PatchEmbedding(nn.Module):
"""
Project strided patches of a multivariate time series into d_model space.

Uses channel-independent patching: each channel's patches are projected
separately with a shared Linear(patch_len, d_model), then averaged across
channels to match the UniTS paper's channel-independent approach.

Parameters
----------
patch_len : int
Length of each patch window in time steps.
stride : int
Stride between consecutive patches.
d_model : int
Output embedding dimension.
dropout : float
Dropout probability.
"""

def __init__(self, patch_len: int, stride: int, d_model: int, dropout: float = 0.1):
super().__init__()
self.patch_len = patch_len
self.stride = stride
self.projection = nn.Linear(patch_len, d_model)
self.drop = nn.Dropout(dropout)

def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
Parameters
----------
x : torch.Tensor
Shape (batch, seq_len, n_channels).

Returns
-------
torch.Tensor
Shape (batch, num_patches, d_model).
"""
patches = x.unfold(dimension=1, size=self.patch_len, step=self.stride)
B, num_patches, C, P = patches.shape
patches = patches.permute(0, 2, 1, 3).contiguous().view(B * C, num_patches, P)
emb = self.drop(self.projection(patches))
emb = emb.view(B, C, num_patches, self.projection.out_features)

# Channel independence: average across channels as per UniTS logic
return emb.mean(dim=1)
26 changes: 26 additions & 0 deletions pytorch_forecasting/layers/_embeddings/_positional_embedding.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,29 @@ def __init__(self, d_model, max_len=5000):

def forward(self, x):
return self.pe[:, : x.size(1)]


class _PositionalEmbedding(PositionalEmbedding):
"""
Sinusoidal positional embedding with additive application and dropout.

Inherits the sinusoidal buffer from ``PositionalEmbedding`` and adds:
- Additive application (x + pe) in ``forward``
- Dropout after addition

Parameters
----------
d_model : int
Embedding dimension.
max_len : int
Maximum sequence length.
dropout : float
Dropout probability.
"""

def __init__(self, d_model: int, max_len: int = 512, dropout: float = 0.1):
super().__init__(d_model, max_len)
self.drop = nn.Dropout(dropout)

def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.drop(x + self.pe[:, : x.size(1), :])
2 changes: 2 additions & 0 deletions pytorch_forecasting/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
)
from pytorch_forecasting.models.tide import TiDEModel
from pytorch_forecasting.models.timexer import TimeXer
from pytorch_forecasting.models.units import UniTS
from pytorch_forecasting.models.xlstm import xLSTMTime

__all__ = [
Expand All @@ -41,5 +42,6 @@
"DecoderMLP",
"TiDEModel",
"TimeXer",
"UniTS",
"xLSTMTime",
]
8 changes: 8 additions & 0 deletions pytorch_forecasting/models/units/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
UniTS: Unified Time Series Model for time series forecasting.
"""

from pytorch_forecasting.models.units._units_pkg_v2 import UniTS_pkg_v2
from pytorch_forecasting.models.units._units_v2 import UniTS

__all__ = ["UniTS", "UniTS_pkg_v2"]
83 changes: 83 additions & 0 deletions pytorch_forecasting/models/units/_units_pkg_v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
Packages container for UniTS model.
"""

from pytorch_forecasting.base._base_pkg import Base_pkg


class UniTS_pkg_v2(Base_pkg):
"""
UniTS: Unified Time Series Model.
Reference: https://arxiv.org/abs/2403.00131
Github: https://github.com/mims-harvard/UniTS
"""

_tags = {
"info:name": "UniTS",
"info:compute": 4,
"authors": ["Muhammad-Rebaal", "gasvn", "sohamukute"],
"python_dependencies": ["torch"],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this? isnt torch already a core dep here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I implemented this seeing the model extension v2. So I think I its an essential tag to be added torch as dependencies, not knowing the core-dep can't be added. Although I removed that.

"capability:exogenous": True,
"capability:multivariate": True,
"capability:pred_int": False,
"capability:flexible_history_length": False,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some tags are missing here. PLease look at the extension-templates

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

"capability:cold_start": False,
}

@classmethod
def get_cls(cls):
from pytorch_forecasting.models.units._units_v2 import UniTS

return UniTS

@classmethod
def get_datamodule_cls(cls):
from pytorch_forecasting.data.data_module import (
EncoderDecoderTimeSeriesDataModule,
)

return EncoderDecoderTimeSeriesDataModule

@classmethod
def get_test_train_params(cls):
"""Return testing parameter settings for the trainer.

Returns
-------
params : dict or list of dict, default = {}
Parameters to create testing instances of the class.
Each dict are parameters to construct an "interesting" test instance, i.e.,
``MyClass(**params)`` or ``MyClass(**params[i])`` creates a valid test
instance. ``create_test_instance`` uses the first (or only) dictionary in
``params``.
"""
params = [
{},
{
Comment thread
phoeenniixx marked this conversation as resolved.
"patch_len": 8,
"stride": 4,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add some loss functions as well here - Is this model only compatible with point prediction losses, or can it also handle quantile and distribution losses?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the paper the model it is mentioned only about point prediction losses but we can extend it so I extend it to both quantile and distribution losses.

},
{
"d_model": 32,
"n_heads": 4,
"patch_len": 8,
"stride": 4,
},
{
"patch_len": 8,
"stride": 4,
"datamodule_cfg": {
"max_encoder_length": 16,
"max_prediction_length": 4,
},
},
]

base_dm_cfg = {"max_encoder_length": 16, "max_prediction_length": 4}

for param in params:
merged = base_dm_cfg.copy()
merged.update(param.get("datamodule_cfg", {}))
param["datamodule_cfg"] = merged

return params
Loading
Loading