2626import torch
2727from torch import Tensor
2828
29- from sdm import Recipe , RelatedTables , Stype , StypeLike , TableTensor , Task
29+ from sdm import (
30+ ColumnarTensor ,
31+ Recipe ,
32+ RelatedTables ,
33+ Stype ,
34+ StypeLike ,
35+ TableTensor ,
36+ Task ,
37+ )
3038from sdm .cache import Cache
39+ from sdm .models import ICLModel
3140from sdm .models ._huggingface import download_checkpoint
32- from sdm .models .base import ICLModel
41+ from sdm .models .callback import Callback
3342from sdm .models .timesfm3 .block import ResidualBlock
3443from sdm .models .timesfm3 .ckpt import remap_ckpt
3544from sdm .models .timesfm3 .icl import ICLBlock
36- from sdm .models .timesfm3 .recipe import default_recipe
45+ from sdm .models .timesfm3 .recipe import TIME_COLUMN , default_recipe
3746from sdm .models .timesfm3 .util import (
3847 gather_future_patches ,
3948 get_running_stats ,
4049 revin ,
4150)
51+ from sdm .processing .execution import MemberContext
4252from sdm .tensor .table import TableSchema
4353
4454
55+ def _with_time_column (table : TableTensor , start : int ) -> TableTensor :
56+ time_steps = torch .arange (
57+ start , start + table .size (- 2 ), device = table .device
58+ ).expand (* table .size ()[:- 2 ], - 1 )
59+ return cast (
60+ TableTensor ,
61+ torch .cat (
62+ (
63+ table ,
64+ TableTensor (
65+ columns = {Stype .id : [TIME_COLUMN ]},
66+ id = ColumnarTensor ((time_steps ,)),
67+ ),
68+ ),
69+ dim = - 1 ,
70+ ),
71+ )
72+
73+
74+ def _drop_time_column (table : TableTensor ) -> TableTensor :
75+ if TIME_COLUMN in table .columns [Stype .id ]:
76+ return table .drop_columns (TIME_COLUMN )
77+ return table
78+
79+
4580class TimesFM3 (ICLModel ):
4681 r"""The multivariate forecasting foundation model from `"TimesFM-3: A
4782 Zero-shot Foundation Model for Multivariate Forecasting"
@@ -149,58 +184,84 @@ def _load_from_pretrained(
149184
150185 return self
151186
152- def forward (self , * args : Any , ** kwargs : Any ) -> TableTensor :
187+ def forward (
188+ self ,
189+ x_context : Tensor | TableTensor ,
190+ y_context : Tensor | TableTensor ,
191+ x_query : Tensor | TableTensor ,
192+ * args : Any ,
193+ ** kwargs : Any ,
194+ ) -> TableTensor :
153195 r""":meta private:""" # noqa: D415
154- x_context = kwargs ["x_context" ] if "x_context" in kwargs else args [0 ]
155196 if not isinstance (x_context , TableTensor ):
156197 x_context = TableTensor .from_tensor (x_context )
157- kwargs ["_x_context_schema" ] = x_context .schema
158-
159- x_query = kwargs ["x_query" ] if "x_query" in kwargs else args [2 ]
198+ if not isinstance (y_context , TableTensor ):
199+ y_context = TableTensor .from_tensor (y_context )
160200 if not isinstance (x_query , TableTensor ):
161201 x_query = TableTensor .from_tensor (x_query )
162- kwargs ["_x_query_schema" ] = x_query .schema
163202
203+ context_length = x_context .size (- 2 )
164204 x_query = expand_query (x_context .schema , x_query )
165205
166- if "x_query" in kwargs :
167- kwargs ["x_query" ] = x_query
168- else :
169- args = (* args [:2 ], x_query , * args [3 :])
170-
171- return super ().forward (* args , ** kwargs ) # type: ignore
206+ out = super ().forward (
207+ _with_time_column (x_context , 0 ),
208+ _with_time_column (y_context , 0 ),
209+ _with_time_column (x_query , context_length ),
210+ * args ,
211+ ** kwargs ,
212+ )
213+ return _drop_time_column (out )
172214
173- def fit (self , * args : Any , ** kwargs : Any ) -> None :
215+ def fit (
216+ self ,
217+ x : Tensor | TableTensor ,
218+ y : Tensor | TableTensor ,
219+ * args : Any ,
220+ ** kwargs : Any ,
221+ ) -> None :
174222 r""":meta private:""" # noqa: D415
175- x = kwargs ["x" ] if "x" in kwargs else args [0 ]
176223 if not isinstance (x , TableTensor ):
177224 x = TableTensor .from_tensor (x )
178- kwargs ["_x_context_schema" ] = x .schema
225+ if not isinstance (y , TableTensor ):
226+ y = TableTensor .from_tensor (y )
179227
180- super ().fit (* args , ** kwargs )
228+ kwargs ["_x_context_schema" ] = x .schema
229+ kwargs ["_context_length" ] = x .size (- 2 )
230+ super ().fit (
231+ _with_time_column (x , 0 ), _with_time_column (y , 0 ), * args , ** kwargs
232+ )
181233
182- def predict (self , * args : Any , ** kwargs : Any ) -> TableTensor :
234+ def predict (
235+ self ,
236+ x : Tensor | TableTensor ,
237+ * args : Any ,
238+ ** kwargs : Any ,
239+ ) -> TableTensor :
183240 r""":meta private:""" # noqa: D415
184241 if self ._cache is None :
185242 raise RuntimeError (
186243 f"{ self .__class__ .__name__ !r} not yet fitted. Make sure to "
187244 f"call '{ self .__class__ .__name__ } .fit()' before."
188245 )
189-
190- x = kwargs ["x" ] if "x" in kwargs else args [0 ]
191246 if not isinstance (x , TableTensor ):
192247 x = TableTensor .from_tensor (x )
193248
194249 cached_kwargs = cast (dict [str , Any ], self ._cache ["kwargs" ])
195- cached_kwargs ["_x_query_schema" ] = x .schema
196250 x = expand_query (cached_kwargs ["_x_context_schema" ], x )
251+ out = super ().predict (
252+ _with_time_column (x , cached_kwargs ["_context_length" ]),
253+ * args ,
254+ ** kwargs ,
255+ )
256+ return _drop_time_column (out )
197257
198- if "x" in kwargs :
199- kwargs ["x" ] = x
200- else :
201- args = (x , * args [1 :])
202-
203- return super ().predict (* args , ** kwargs )
258+ def _prepare_context (
259+ self ,
260+ context : MemberContext ,
261+ callbacks : Sequence [Callback ],
262+ ) -> MemberContext :
263+ context = context ._replace (y = _drop_time_column (context .y ))
264+ return super ()._prepare_context (context , callbacks )
204265
205266 def _forward (
206267 self ,
@@ -212,7 +273,7 @@ def _forward(
212273 cache : Cache | None ,
213274 generator : torch .Generator | None ,
214275 ** kwargs : Any ,
215- ) -> TableTensor : # [..., R_query, Y * 9 ]
276+ ) -> TableTensor : # [..., R_query, T * Q ]
216277
217278 if y_context is not None :
218279 columns = y_context .columns [Stype .numerical ]
@@ -227,18 +288,26 @@ def _forward(
227288 assert x_context is not None
228289 size = (* x_context .size ()[:- 2 ], 0 )
229290
230- return TableTensor (
291+ out = TableTensor (
231292 columns = {
232293 Stype .numerical : [
233- f"{ name } __q{ i } "
234- for name , i in product (columns , range ( 10 , 100 , 10 ) )
294+ f"{ name } __q{ int ( 100 * q ) } "
295+ for name , q in product (columns , self . model . quantiles )
235296 ]
236297 },
237298 numerical = torch .zeros (
238- (* size , len (columns ) * 9 ),
299+ (* size , len (columns ) * len ( self . model . quantiles ) ),
239300 device = next (self .parameters ()).device ,
240301 ),
241302 )
303+ if x_query is None :
304+ return out
305+ context_length = (
306+ x_context .size (- 2 )
307+ if x_context is not None
308+ else kwargs ["_context_length" ]
309+ )
310+ return _with_time_column (out , context_length )
242311
243312
244313class _TimesFM3 (torch .nn .Module ):
@@ -310,7 +379,9 @@ def _preprocess(
310379 count , mean , std = get_running_stats (x , mask )
311380
312381 # Gather the prediction patches and hide unavailable values
313- horizon_x , past_end = gather_future_patches (x , self .num_horizon_patches )
382+ horizon_x , past_end = gather_future_patches (
383+ x , self .num_horizon_patches
384+ )
314385 horizon_mask , _ = gather_future_patches (mask , self .num_horizon_patches )
315386 horizon_mask .logical_or_ (context_only [..., None ])
316387 horizon_mask .logical_or_ (past_end )
0 commit comments