Time Series Forecasting
Transformers
Safetensors
tabby
feature-extraction
time-series
foundation-model
probabilistic-forecasting
quantile-regression
patchtst
custom_code
Instructions to use paris-noah/Tabby with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use paris-noah/Tabby with Transformers:
# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("paris-noah/Tabby", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 6,717 Bytes
a5d67fc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | # Compatibility adapter between `train_mixup.py` and the HF-style model files.
#
# The pretraining script expects (and previously had) a module
# `models/PatchTSTFM.py` exposing:
#
# * ``PatchTSTFMConfig`` -- a **dataclass** (the trainer calls
# ``dataclasses.asdict(cfg)`` when checkpointing) with fields
# ``context_length, patch_size, num_layers, d_model, head_dim, dropout,
# num_quantiles, mask_ratio, cpm_blocks, eps`` and properties
# ``num_patches`` / ``num_heads``. ``utils/input_preprocessing.py`` reads
# ``cfg.patch_size``, ``cfg.num_patches``, ``cfg.mask_ratio``,
# ``cfg.cpm_blocks`` and ``cfg.eps`` from it.
#
# * ``PatchTSTFM`` -- an ``nn.Module`` with
# ``forward(x_norm, union_mask, patch_key_padding_mask) -> q_hat`` where
# ``x_norm`` is FloatTensor[B, T], already asinh-normalized outside the
# model and zeroed at masked/missing/padded positions,
# ``union_mask`` is BoolTensor[B, T] (pred | missing | padding),
# ``patch_key_padding_mask`` is BoolTensor[B, N] (True = fully padded
# patch), and
# ``q_hat`` is FloatTensor[B, T, num_quantiles], monotone in the last
# dim, in the *normalized* space (the pinball loss and the
# inverse transform live in the training script / utils).
#
# The refactored model files (``configuration_patchtst_fm.py`` /
# ``modeling_patchtst_fm.py``) expose a different vocabulary
# (``d_patch, n_layer, n_head, num_quantile, ...``) and a
# ``PatchTSTFMModel.forward`` that additionally performs RevIN normalization
# internally -- which would double-normalize the trainer's already-normalized
# input. This adapter therefore (i) maps the trainer's config fields onto the
# HF config, and (ii) drives the *sub-modules* of ``PatchTSTFMModel``
# (patch embedding, positional embedding, transformer blocks, monotone
# quantile head) exactly as ``PatchTSTFMModel.decode`` does, while leaving
# normalization to ``utils.input_preprocessing`` as the pretraining logic
# requires. Because the trainable module is held under ``self.backbone``,
# checkpoints saved by ``train_mixup.py`` have ``backbone.*`` keys and load
# directly into ``PatchTSTFMForPrediction`` for inference.
#
# One deliberate difference from ``PatchTSTFMModel.decode``: attention is
# masked on *keys only*. ``decode`` masks padded queries as well, so a padded
# patch's attention row is entirely ``-inf``; depending on the PyTorch
# version/SDPA backend, fully masked rows may yield NaN outputs (some builds
# zero-fill them instead). At inference the padded region is sliced away, but
# during pretraining the loss is computed as ``loss * loss_mask`` and
# ``NaN * 0 = NaN``, so a single left-padded real series could poison the
# batch loss on such backends. Key-only masking (the convention of the
# original pretraining model, cf. ``src_key_padding_mask`` in
# ``nn.TransformerEncoder``) keeps padded-query outputs finite on every
# backend; they carry no gradient because ``pred_mask`` never overlaps
# padding.
from __future__ import annotations
from dataclasses import dataclass
import torch
import torch.nn as nn
from .configuration_patchtst_fm import PatchTSTFMConfig as HFPatchTSTFMConfig
from .modeling_patchtst_fm import PatchTSTFMModel
@dataclass
class PatchTSTFMConfig:
"""Trainer-facing configuration (dataclass, as `asdict` requires)."""
context_length: int = 8192
patch_size: int = 16
num_layers: int = 20
d_model: int = 1024
head_dim: int = 64
mlp_ratio: float = 4.0
dropout: float = 0.1
num_quantiles: int = 99
mask_ratio: float = 0.4
cpm_blocks: int = 8
eps: float = 1e-5
def __post_init__(self) -> None:
if self.context_length % self.patch_size != 0:
raise ValueError("context_length must be divisible by patch_size.")
if self.d_model % self.head_dim != 0:
raise ValueError("d_model must be divisible by head_dim.")
@property
def num_patches(self) -> int:
return self.context_length // self.patch_size
@property
def num_heads(self) -> int:
return self.d_model // self.head_dim
def to_hf_config(self) -> HFPatchTSTFMConfig:
"""Map trainer field names onto the HF-style config vocabulary."""
return HFPatchTSTFMConfig(
context_length=self.context_length,
d_patch=self.patch_size,
d_model=self.d_model,
n_head=self.num_heads,
n_layer=self.num_layers,
pretrain_mask_ratio=self.mask_ratio,
pretrain_mask_cont=self.cpm_blocks,
num_quantile=self.num_quantiles,
dropout=self.dropout, # read via getattr in modeling_patchtst_fm
)
class PatchTSTFM(nn.Module):
"""Pretraining wrapper around the HF-style ``PatchTSTFMModel`` backbone."""
def __init__(self, cfg: PatchTSTFMConfig):
super().__init__()
self.config = cfg
self.backbone = PatchTSTFMModel(cfg.to_hf_config())
def forward(
self,
x_norm: torch.Tensor,
union_mask: torch.Tensor,
patch_key_padding_mask: torch.Tensor,
) -> torch.Tensor:
"""Return normalized quantile predictions of shape [B, T, K].
Mirrors ``PatchTSTFMModel.decode`` (same sub-modules, same monotone
quantile construction), with key-only attention padding masking; see
the module docstring for the rationale.
"""
cfg = self.config
B, T = x_norm.shape
if T != cfg.context_length:
raise ValueError(f"Expected T={cfg.context_length}, got {T}.")
L, N, K = cfg.patch_size, cfg.num_patches, cfg.num_quantiles
bb = self.backbone
value_patch = x_norm.reshape(B, N, L)
mask_patch = union_mask.reshape(B, N, L).to(x_norm.dtype)
# Same input convention as PatchTSTFMModel.decode: cat([values, 1 - mask]).
h = bb.in_layer(torch.cat([value_patch, 1.0 - mask_patch], dim=-1))
h = bb.pos_embed(h)
# Key-only boolean attention mask, broadcast to (B, n_head, N, N):
# True = position may be attended to.
attn_mask = ~patch_key_padding_mask.bool()[:, None, None, :]
for block in bb.blocks:
h = block(h, attn_mask)
h = bb.out_layer(h)
# Monotone quantile head, identical to PatchTSTFMModel.decode.
q_raw = h.reshape(B, N, K + 1, L).permute(0, 2, 1, 3) # (B, K+1, N, L)
q = q_raw[:, 0, :, :].unsqueeze(1) + torch.cumsum(
nn.functional.softplus(q_raw[:, 1:, :, :]) / K, dim=1
) # (B, K, N, L)
q_hat = q.permute(0, 2, 3, 1).reshape(B, T, K) # (B, T, K)
return q_hat
|