Aluode/PerceptionLabPortable
0
1# coding=utf-82# Copyright 2025 Google LLC and HuggingFace Inc. team.3#4# Licensed under the Apache License, Version 2.0 (the "License");5# you may not use this file except in compliance with the License.6# You may obtain a copy of the License at7#8# http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing, software11# distributed under the License is distributed on an "AS IS" BASIS,12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13# See the License for the specific language governing permissions and14# limitations under the License.15"""PyTorch TimesFM model."""16 17import math18from collections.abc import Sequence19from dataclasses import dataclass20from typing import Callable, Optional, Union21 22import torch23import torch.nn as nn24import torch.nn.functional as F25 26from ...modeling_flash_attention_utils import FlashAttentionKwargs27from ...modeling_outputs import BaseModelOutput28from ...modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel29from ...processing_utils import Unpack30from ...utils import auto_docstring, can_return_tuple, logging31from ..llama.modeling_llama import LlamaRMSNorm32from ..phi4_multimodal.modeling_phi4_multimodal import simple_eager_attention_forward33from .configuration_timesfm import TimesFmConfig34 35 36logger = logging.get_logger(__name__)37 38 39@dataclass40@auto_docstring41class TimesFmOutput(BaseModelOutput):42 r"""43 loc (`torch.Tensor` of shape `(batch_size, )`):44 The mean of the time series inputs.45 scale (`torch.Tensor` of shape `(batch_size,)`):46 The scale of the time series inputs.47 """48 49 loc: Optional[torch.Tensor] = None50 scale: Optional[torch.Tensor] = None51 52 53@dataclass54@auto_docstring55class TimesFmOutputForPrediction(BaseModelOutput):56 r"""57 mean_predictions (`torch.Tensor` of shape `(batch_size, sequence_length)`):58 The mean predictions of the time series.59 full_predictions (`torch.Tensor` of shape `(batch_size, sequence_length)`):60 The full predictions of the time series including the mean and the quantiles.61 loss (`torch.Tensor` of shape `(1,)`, *optional*, returned when `future_values` is provided):62 The loss of the TimesFM model.63 """64 65 mean_predictions: Optional[torch.Tensor] = None66 full_predictions: Optional[torch.Tensor] = None67 loss: Optional[Union[torch.Tensor, float]] = None68 69 70class TimesFmMLP(nn.Module):71 """Pax MLP in pytorch."""72 73 def __init__(self, config: TimesFmConfig):74 super().__init__()75 hidden_size = config.hidden_size76 intermediate_size = config.intermediate_size77 78 self.gate_proj = nn.Linear(hidden_size, intermediate_size)79 self.down_proj = nn.Linear(intermediate_size, hidden_size)80 self.layer_norm = nn.LayerNorm(normalized_shape=hidden_size, eps=1e-6)81 82 def forward(self, x, paddings=None):83 gate_inp = self.layer_norm(x)84 gate = self.gate_proj(gate_inp)85 gate = F.relu(gate)86 outputs = self.down_proj(gate)87 if paddings is not None:88 outputs = outputs * (1.0 - paddings[:, :, None])89 return outputs + x90 91 92class TimesFmResidualBlock(nn.Module):93 """TimesFM residual block."""94 95 def __init__(self, input_dims, hidden_dims, output_dims):96 super().__init__()97 self.input_dims = input_dims98 self.hidden_dims = hidden_dims99 self.output_dims = output_dims100 101 self.input_layer = nn.Linear(input_dims, hidden_dims)102 self.activation = nn.SiLU()103 self.output_layer = nn.Linear(hidden_dims, output_dims)104 self.residual_layer = nn.Linear(input_dims, output_dims)105 106 def forward(self, x):107 hidden = self.input_layer(x)108 hidden = self.activation(hidden)109 output = self.output_layer(hidden)110 residual = self.residual_layer(x)111 return output + residual112 113 114class TimesFmRMSNorm(LlamaRMSNorm):115 pass116 117 118class TimesFmPositionalEmbedding(nn.Module):119 """Generates position embedding for a given 1-d sequence."""120 121 def __init__(self, config: TimesFmConfig):122 super().__init__()123 min_timescale = config.min_timescale124 max_timescale = config.max_timescale125 self.embedding_dims = config.hidden_size126 127 num_timescales = self.embedding_dims // 2128 log_timescale_increment = math.log(float(max_timescale) / float(min_timescale)) / max(num_timescales - 1, 1)129 self.register_buffer(130 "inv_timescales",131 min_timescale * torch.exp(torch.arange(num_timescales, dtype=torch.float32) * -log_timescale_increment),132 )133 134 def forward(self, seq_length=None, position=None):135 """Generates a Tensor of sinusoids with different frequencies.136 137 Args:138 seq_length: an optional Python int defining the output sequence length.139 if the `position` argument is specified.140 position: [B, seq_length], optional position for each token in the141 sequence, only required when the sequence is packed.142 143 Returns:144 [B, seqlen, D] if `position` is specified, else [1, seqlen, D]145 """146 if position is None and seq_length is None:147 raise ValueError("Either position or seq_length must be provided")148 149 if position is None:150 # [1, seqlen]151 position = torch.arange(seq_length, dtype=torch.float32, device=self.inv_timescales.device).unsqueeze(0)152 elif position.ndim != 2:153 raise ValueError(f"position must be 2-dimensional, got shape {position.shape}")154 155 scaled_time = position.view(*position.shape, 1) * self.inv_timescales.view(1, 1, -1)156 signal = torch.cat([torch.sin(scaled_time), torch.cos(scaled_time)], dim=2)157 158 # Padding to ensure correct embedding dimension159 signal = F.pad(signal, (0, 0, 0, self.embedding_dims % 2))160 return signal161 162 163class TimesFmAttention(nn.Module):164 """Implements the attention used in TimesFM. One key difference is that there is _per_dim_scaling of the query."""165 166 def __init__(self, config: TimesFmConfig, layer_idx: int):167 super().__init__()168 self.config = config169 self.is_causal = True170 self.attention_dropout = config.attention_dropout171 self.layer_idx = layer_idx172 173 self.num_heads = config.num_attention_heads174 self.hidden_size = config.hidden_size175 self.head_dim = config.head_dim176 177 self.q_size = self.num_heads * self.head_dim178 self.kv_size = self.num_heads * self.head_dim179 self.scaling = nn.Parameter(torch.empty((self.head_dim,)))180 181 self.q_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim)182 self.k_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim)183 self.v_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim)184 self.o_proj = nn.Linear(self.num_heads * self.head_dim, self.hidden_size)185 186 def _scale_query(self, query: torch.Tensor) -> torch.Tensor:187 scale = F.softplus(self.scaling).mul(1.442695041 / math.sqrt(self.head_dim))188 return query * scale[None, None, None, :]189 190 def forward(191 self,192 hidden_states: torch.Tensor,193 attention_mask: Optional[torch.Tensor] = None,194 **kwargs: Unpack[FlashAttentionKwargs],195 ) -> tuple[torch.Tensor, Optional[torch.Tensor]]:196 input_shape = hidden_states.shape[:-1]197 hidden_shape = (*input_shape, -1, self.head_dim)198 199 query_states = self.q_proj(hidden_states).view(hidden_shape).transpose(1, 2)200 query_states = self._scale_query(query_states)201 key_states = self.k_proj(hidden_states).view(hidden_shape).transpose(1, 2)202 value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)203 204 attention_interface: Callable = simple_eager_attention_forward205 if self.config._attn_implementation != "eager":206 attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]207 208 attn_output, attn_weights = attention_interface(209 self,210 query_states,211 key_states,212 value_states,213 attention_mask,214 dropout=0.0 if not self.training else self.attention_dropout,215 scaling=1.0,216 **kwargs,217 )218 attn_output = attn_output.reshape(*input_shape, -1).contiguous()219 attn_output = self.o_proj(attn_output)220 return attn_output, attn_weights221 222 223class TimesFmDecoderLayer(nn.Module):224 """Transformer layer."""225 226 def __init__(self, config: TimesFmConfig, layer_idx: int):227 super().__init__()228 229 self.self_attn = TimesFmAttention(config, layer_idx=layer_idx)230 self.mlp = TimesFmMLP(config)231 self.input_layernorm = TimesFmRMSNorm(config.hidden_size, eps=config.rms_norm_eps)232 233 def forward(234 self,235 hidden_states: torch.Tensor,236 attention_mask: torch.Tensor,237 paddings: torch.Tensor,238 output_attentions: bool = False,239 ) -> tuple[Optional[torch.Tensor], torch.Tensor]:240 # Self Attention241 residual = hidden_states242 hidden_states = self.input_layernorm(hidden_states)243 hidden_states, scores = self.self_attn(244 hidden_states=hidden_states,245 attention_mask=attention_mask,246 output_attentions=output_attentions,247 )248 hidden_states = residual + hidden_states249 250 # MLP251 hidden_states = self.mlp(hidden_states, paddings=paddings)252 253 return scores, hidden_states254 255 256@auto_docstring257class TimesFmPreTrainedModel(PreTrainedModel):258 config: TimesFmConfig259 base_model_prefix = "timesfm"260 _no_split_modules = ["TimesFmDecoderLayer"]261 main_input_name = "past_values"262 _supports_sdpa = True263 264 def _init_weights(self, module):265 super()._init_weights(module)266 if isinstance(module, TimesFmAttention):267 # Initialize scaling parameter268 nn.init.ones_(module.scaling)269 270 271@auto_docstring272class TimesFmModel(TimesFmPreTrainedModel):273 def __init__(self, config: TimesFmConfig):274 super().__init__(config)275 276 self.config = config277 self.input_ff_layer = TimesFmResidualBlock(278 input_dims=2 * config.patch_length,279 output_dims=config.hidden_size,280 hidden_dims=config.intermediate_size,281 )282 self.freq_emb = nn.Embedding(num_embeddings=config.freq_size, embedding_dim=config.hidden_size)283 self.layers = nn.ModuleList(284 [TimesFmDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]285 )286 if self.config.use_positional_embedding:287 self.position_emb = TimesFmPositionalEmbedding(config=config)288 289 # Initialize weights and apply final processing290 self.post_init()291 292 def _forward_transform(293 self, inputs: torch.Tensor, patched_pads: torch.Tensor294 ) -> tuple[torch.Tensor, tuple[torch.Tensor, torch.Tensor]]:295 """Input is of shape [B, N, P]."""296 mu, sigma = self._timesfm_masked_mean_std(inputs, patched_pads)297 sigma = torch.where(298 sigma < self.config.tolerance,299 torch.tensor(1.0, dtype=sigma.dtype, device=sigma.device),300 sigma,301 )302 303 # Normalize each patch304 outputs = (inputs - mu[:, None, None]) / sigma[:, None, None]305 outputs = torch.where(306 torch.abs(inputs - self.config.pad_val) < self.config.tolerance,307 torch.tensor(self.config.pad_val, dtype=outputs.dtype, device=outputs.device),308 outputs,309 )310 return outputs, (mu, sigma)311 312 @can_return_tuple313 @auto_docstring314 def forward(315 self,316 past_values: torch.Tensor,317 past_values_padding: torch.LongTensor,318 freq: torch.Tensor,319 output_attentions: bool = False,320 output_hidden_states: bool = False,321 ) -> TimesFmOutput:322 r"""323 past_values (`torch.FloatTensor` of shape `(batch_size, sequence_length)`):324 Past values of the time series that serves as input to the model.325 past_values_padding (`torch.LongTensor` of shape `(batch_size, sequence_length)`):326 The padding indicator of the time series.327 freq (`torch.LongTensor` of shape `(batch_size,)`):328 Frequency indices for the time series data.329 """330 # Reshape into patches (using view for efficiency)331 bsize = past_values.shape[0]332 patched_inputs = past_values.view(bsize, -1, self.config.patch_length)333 patched_pads = past_values_padding.view(bsize, -1, self.config.patch_length)334 335 patched_inputs = torch.where(336 torch.abs(patched_pads - 1.0) < self.config.tolerance,337 torch.tensor(0.0, dtype=patched_inputs.dtype, device=patched_inputs.device),338 patched_inputs,339 )340 patched_pads = torch.where(341 torch.abs(patched_inputs - self.config.pad_val) < self.config.tolerance,342 torch.tensor(1.0, dtype=patched_pads.dtype, device=patched_pads.device),343 patched_pads,344 )345 patched_inputs, stats = self._forward_transform(patched_inputs, patched_pads)346 347 # B x N x D348 patched_inputs = patched_inputs * (1.0 - patched_pads)349 concat_inputs = torch.cat([patched_inputs, patched_pads], dim=-1)350 model_input = self.input_ff_layer(concat_inputs)351 352 # A patch should not be padded even if there is at least one zero.353 patched_padding = torch.min(patched_pads, dim=-1)[0] # Get the values from the min result354 if self.config.use_positional_embedding:355 pos_emb = self.position_emb(model_input.shape[1])356 pos_emb = torch.concat([pos_emb] * model_input.shape[0], dim=0)357 pos_emb = self._timesfm_shift_padded_seq(patched_padding, pos_emb)358 model_input += pos_emb359 360 f_emb = self.freq_emb(freq) # B x 1 x D361 model_input += f_emb362 363 # Convert paddings to attention mask and combine with causal mask364 hidden_states = model_input365 attention_mask = self._prepare_4d_attention_mask(366 attention_mask=patched_padding,367 sequence_length=hidden_states.shape[1],368 dtype=hidden_states.dtype,369 device=hidden_states.device,370 is_causal=True,371 )372 373 all_attentions = []374 all_hidden_states = []375 376 for layer in self.layers[: self.config.num_hidden_layers]:377 scores, hidden_states = layer(378 hidden_states=hidden_states,379 attention_mask=attention_mask,380 paddings=patched_padding,381 output_attentions=output_attentions,382 )383 if output_attentions:384 all_attentions.append(scores)385 if output_hidden_states:386 all_hidden_states.append(hidden_states)387 388 if output_hidden_states:389 all_hidden_states = [model_input] + all_hidden_states390 else:391 all_hidden_states = None392 393 return TimesFmOutput(394 last_hidden_state=hidden_states,395 hidden_states=all_hidden_states,396 attentions=all_attentions if output_attentions else None,397 loc=stats[0],398 scale=stats[1],399 )400 401 @staticmethod402 def _prepare_4d_attention_mask(403 attention_mask: Optional[torch.Tensor],404 sequence_length: int,405 dtype: torch.dtype,406 device: torch.device,407 is_causal: bool = True,408 ) -> Optional[torch.Tensor]:409 """410 Creates 4D attention mask and combines causal and padding masks if needed.411 412 Args:413 attention_mask: Optional tensor of shape (batch_size, seq_length) containing padding mask414 sequence_length: Length of the sequence415 dtype: Data type of the mask416 device: Device of the mask417 is_causal: Whether to apply causal masking418 419 Returns:420 4D attention mask of shape (batch_size, 1, seq_length, seq_length)421 """422 # Get minimum value for the dtype423 min_value = torch.finfo(dtype).min if dtype.is_floating_point else torch.iinfo(dtype).min424 425 # Handle padding mask426 if attention_mask is not None:427 # Convert 2D padding mask to 4D attention mask428 attention_mask = attention_mask.view(attention_mask.shape[0], 1, 1, -1)429 attention_mask = attention_mask * min_value430 431 # Create causal mask if needed432 if is_causal:433 causal_mask = torch.triu(434 torch.ones((sequence_length, sequence_length), dtype=dtype, device=device) * min_value,435 diagonal=1,436 )437 causal_mask = causal_mask.view(1, 1, sequence_length, sequence_length)438 439 # Combine with padding mask if it exists440 if attention_mask is not None:441 attention_mask = torch.minimum(attention_mask, causal_mask)442 else:443 attention_mask = causal_mask444 445 return attention_mask446 447 @staticmethod448 def _timesfm_masked_mean_std(inputs: torch.Tensor, padding: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:449 """Calculates mean and standard deviation of `inputs` across axis 1.450 451 It excludes values where `padding` is 1.452 453 Args:454 inputs: A PyTorch tensor of shape [b, n, p].455 padding: A PyTorch tensor of shape [b, n, p] with values 0 or 1.456 457 Returns:458 A tuple containing the mean and standard deviation.459 We return the statistics of the first patch with more than three non-padded values.460 """461 462 # Selecting the first patch with more than 3 unpadded values.463 def _get_patch_index(arr: torch.Tensor):464 indices = torch.argmax((arr >= 3).to(torch.int32), dim=1)465 row_sum = (arr >= 3).to(torch.int32).sum(dim=1)466 return torch.where(row_sum == 0, arr.shape[1] - 1, indices)467 468 pad_sum = torch.sum(1 - padding, dim=2)469 patch_indices = _get_patch_index(pad_sum)470 bidxs = torch.arange(inputs.shape[0])471 472 arr = inputs[bidxs, patch_indices, :]473 pad = padding[bidxs, patch_indices, :]474 475 # Create a mask where padding is 0476 mask = 1 - pad477 478 # Calculate the number of valid elements479 num_valid_elements = torch.sum(mask, dim=1)480 num_valid_elements = torch.where(481 num_valid_elements == 0,482 torch.tensor(1, dtype=num_valid_elements.dtype, device=num_valid_elements.device),483 num_valid_elements,484 )485 486 # Calculate the masked sum and squared sum487 masked_sum = torch.sum(arr * mask, dim=1)488 masked_squared_sum = torch.sum((arr * mask) ** 2, dim=1)489 490 # Calculate the masked mean and standard deviation491 masked_mean = masked_sum / num_valid_elements492 masked_var = masked_squared_sum / num_valid_elements - masked_mean**2493 masked_var = torch.where(494 masked_var < 0.0,495 torch.tensor(0.0, dtype=masked_var.dtype, device=masked_var.device),496 masked_var,497 )498 masked_std = torch.sqrt(masked_var)499 500 return masked_mean, masked_std501 502 @staticmethod503 def _timesfm_shift_padded_seq(mask: torch.Tensor, seq: torch.Tensor) -> torch.Tensor:504 """Shifts rows of seq based on the first 0 in each row of the mask.505 506 Args:507 mask: mask tensor of shape [B, N]508 seq: seq tensor of shape [B, N, P]509 510 Returns:511 The shifted sequence.512 """513 batch_size, num_seq, feature_dim = seq.shape514 515 new_mask: torch.BoolTensor = mask == 0516 517 # Use argmax to find the first True value in each row518 indices = new_mask.to(torch.int32).argmax(dim=1)519 520 # Handle rows with all zeros521 indices[~new_mask.any(dim=1)] = -1522 523 # Create index ranges for each sequence in the batch524 idx_range = torch.arange(num_seq, device=seq.device).view(1, -1, 1).expand(batch_size, -1, feature_dim)525 526 # Calculate shifted indices for each element in each sequence527 shifted_idx = (idx_range - indices[:, None, None]) % num_seq528 529 # Gather values from seq using shifted indices530 shifted_seq = seq.gather(1, shifted_idx)531 532 return shifted_seq533 534 535class TimesFmModelForPrediction(TimesFmPreTrainedModel):536 """TimesFM model for quantile and mean prediction."""537 538 def __init__(self, config: TimesFmConfig):539 super().__init__(config)540 541 self.config = config542 self.context_len = config.context_length543 self.horizon_len = config.horizon_length544 545 self.decoder = TimesFmModel(config)546 547 # quantile and mean output548 self.horizon_ff_layer = TimesFmResidualBlock(549 input_dims=config.hidden_size,550 output_dims=config.horizon_length * (1 + len(config.quantiles)),551 hidden_dims=config.intermediate_size,552 )553 554 # Initialize weights and apply final processing555 self.post_init()556 557 def _preprocess(558 self, inputs: Sequence[torch.Tensor], freq: Sequence[int]559 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:560 """Formats and pads raw inputs to feed into the model.561 562 This function both pads each time series to match the context length, and563 pads the inputs to meet the SPMD shape requirement.564 565 Args:566 inputs: A list of 1d Tensors. Each Tensor is the context time series of567 a single forecast task.568 freq: list of frequencies569 570 Returns:571 A tuple of:572 - the padded input time series to meet the model required context.573 - the padding indicator.574 - the number of padded examples for SPMD so that each core has the same575 number (a multiple of `batch_size`) of examples.576 """577 input_ts, input_padding, inp_freq = [], [], []578 579 for i, ts in enumerate(inputs):580 input_len = ts.shape[0]581 padding = torch.zeros(input_len + self.horizon_len, dtype=ts.dtype, device=ts.device)582 if input_len < self.context_len:583 num_front_pad = self.context_len - input_len584 ts = torch.cat([torch.zeros(num_front_pad, dtype=ts.dtype, device=ts.device), ts], dim=0)585 padding = torch.cat([torch.ones(num_front_pad, dtype=ts.dtype, device=padding.device), padding], dim=0)586 elif input_len > self.context_len:587 ts = ts[-self.context_len :]588 padding = padding[-(self.context_len + self.horizon_len) :]589 590 input_ts.append(ts)591 input_padding.append(padding)592 inp_freq.append(freq[i])593 594 return (595 torch.stack(input_ts, dim=0),596 torch.stack(input_padding, dim=0),597 torch.tensor(inp_freq, dtype=torch.int32).reshape(-1, 1),598 )599 600 def _postprocess_output(601 self, model_output: torch.Tensor, stats: tuple[torch.Tensor, torch.Tensor]602 ) -> torch.Tensor:603 """Postprocess output of stacked transformer."""604 605 # B x N x (H.Q)606 output_ts = self.horizon_ff_layer(model_output)607 608 # Reshape using view609 b, n, _ = output_ts.shape610 output_ts = output_ts.view(b, n, self.config.horizon_length, len(self.config.quantiles) + 1)611 612 mu, sigma = stats613 return output_ts * sigma[:, None, None, None] + mu[:, None, None, None]614 615 def _quantile_loss(self, predictions: torch.Tensor, targets: torch.Tensor) -> torch.Tensor:616 losses = []617 for i, q in enumerate(self.config.quantiles):618 errors = targets - predictions[..., i]619 loss = torch.max((q - 1) * errors, q * errors)620 losses.append(loss.mean())621 return torch.stack(losses).mean()622 623 @can_return_tuple624 @auto_docstring625 def forward(626 self,627 past_values: Sequence[torch.Tensor],628 freq: Optional[Sequence[Union[torch.Tensor, int]]] = None,629 window_size: Optional[int] = None,630 future_values: Optional[torch.Tensor] = None,631 forecast_context_len: Optional[int] = None,632 return_forecast_on_context: bool = False,633 truncate_negative: bool = False,634 output_attentions: Optional[bool] = None,635 output_hidden_states: Optional[bool] = None,636 ) -> TimesFmOutputForPrediction:637 r"""638 past_values (`torch.FloatTensor` of shape `(batch_size, sequence_length)`):639 Past values of the time series that serves as input to the model.640 freq (`torch.LongTensor` of shape `(batch_size,)`):641 Frequency indices for the time series data.642 window_size (`int`, *optional*):643 Window size of trend + residual decomposition. If None then we do not do decomposition.644 future_values (`torch.Tensor`, *optional*):645 Optional future time series values to be used for loss computation.646 forecast_context_len (`int`, *optional*):647 Optional max context length.648 return_forecast_on_context (`bool`, *optional*):649 True to return the forecast on the context when available, i.e. after the first input patch.650 truncate_negative (`bool`, *optional*):651 Truncate to only non-negative values if any of the contexts have non-negative values,652 otherwise do nothing.653 output_attentions (`bool`, *optional*):654 Whether to output the attentions.655 output_hidden_states (`bool`, *optional*):656 Whether to output the hidden states.657 658 Example:659 660 ```python661 >>> from transformers import TimesFmModelForPrediction662 663 >>> model = TimesFmModelForPrediction.from_pretrained("google/timesfm-2.0-500m-pytorch")664 665 >>> forecast_input = [torch.linspace(0, 20, 100).sin(), torch.linspace(0, 20, 200).sin(), torch.linspace(0, 20, 400).sin()]666 >>> frequency_input = torch.tensor([0, 1, 2], dtype=torch.long)667 668 >>> # Generate669 >>> with torch.no_grad():670 >>> outputs = model(past_values=forecast_input, freq=frequency_input, return_dict=True)671 >>> point_forecast_conv = outputs.mean_predictions672 >>> quantile_forecast_conv = outputs.full_predictions673 ```674 """675 if forecast_context_len is None:676 fcontext_len = self.context_len677 else:678 fcontext_len = forecast_context_len679 680 # Get device from first input tensor681 device = past_values[0].device682 683 # Truncate inputs to forecast_context_len684 inputs = [ts[-fcontext_len:] for ts in past_values]685 inp_min = torch.min(torch.stack([torch.min(ts) for ts in inputs]))686 687 if window_size is not None:688 new_inputs = []689 new_freqs = []690 for i, ts in enumerate(inputs):691 new_inputs.extend(self._timesfm_moving_average(ts, window_size))692 if freq is not None:693 new_freqs.extend([freq[i]] * 2)694 inputs = new_inputs695 if freq is not None:696 freq = new_freqs697 698 if freq is None:699 logger.info("No frequency provided via `freq`. Default to high (0).")700 freq = [0] * len(inputs)701 702 if output_attentions is None:703 output_attentions = self.config.output_attentions704 if output_hidden_states is None:705 output_hidden_states = self.config.output_hidden_states706 707 input_ts, input_padding, inp_freq = self._preprocess(inputs, freq)708 # Move tensors to the same device as input709 input_ts = input_ts.to(device)710 input_padding = input_padding.to(device)711 inp_freq = inp_freq.to(device)712 713 final_out = input_ts714 context_len = final_out.shape[1]715 full_outputs = []716 717 if input_padding.shape[1] != final_out.shape[1] + self.horizon_len:718 raise ValueError(719 "Length of paddings must match length of input + horizon_len:"720 f" {input_padding.shape[1]} != {final_out.shape[1]} + {self.horizon_len}"721 )722 output_patch_len = self.config.horizon_length723 724 num_decode_patches = (self.horizon_len + output_patch_len - 1) // output_patch_len725 for step_index in range(num_decode_patches):726 current_padding = input_padding[:, 0 : final_out.shape[1]]727 input_ts = final_out[:, -fcontext_len:]728 input_padding = current_padding[:, -fcontext_len:]729 decoder_output = self.decoder(730 past_values=input_ts,731 past_values_padding=input_padding,732 freq=inp_freq,733 output_attentions=output_attentions,734 output_hidden_states=output_hidden_states,735 )736 fprop_outputs = self._postprocess_output(737 decoder_output.last_hidden_state,738 (decoder_output.loc, decoder_output.scale),739 )740 741 if return_forecast_on_context and step_index == 0:742 # For the first decodings step, collect the model forecast on the743 # context except the unavailable first input batch forecast.744 new_full_ts = fprop_outputs[:, :-1, : self.config.patch_length, :]745 # We have to use reshape and not view for non-contiguous memory746 new_full_ts = new_full_ts.reshape(new_full_ts.size(0), -1, new_full_ts.size(3))747 748 full_outputs.append(new_full_ts)749 750 # (full batch, last patch, output_patch_len, index of mean forecast = 0)751 new_ts = fprop_outputs[:, -1, :output_patch_len, 0]752 new_full_ts = fprop_outputs[:, -1, :output_patch_len, :]753 # (full batch, last patch, output_patch_len, all output indices)754 full_outputs.append(new_full_ts)755 final_out = torch.concatenate([final_out, new_ts], axis=-1)756 757 if return_forecast_on_context:758 # `full_outputs` indexing starts at after the first input patch.759 full_outputs = torch.concatenate(full_outputs, axis=1)[760 :, : (context_len - self.config.patch_length + self.horizon_len), :761 ]762 else:763 # `full_outputs` indexing starts at the forecast horizon.764 full_outputs = torch.concatenate(full_outputs, axis=1)[:, 0 : self.horizon_len, :]765 766 mean_outputs = full_outputs[:, :, 0]767 if window_size is not None:768 mean_outputs = mean_outputs[0::2, ...] + mean_outputs[1::2, ...]769 full_outputs = full_outputs[0::2, ...] + full_outputs[1::2, ...]770 if inp_min >= 0 and truncate_negative:771 mean_outputs = torch.maximum(mean_outputs, 0.0)772 full_outputs = torch.maximum(full_outputs, 0.0)773 774 loss = None775 if future_values is not None:776 mse_loss = F.mse_loss(mean_outputs, future_values)777 quantile_loss = self._quantile_loss(full_outputs[:, :, 1:], future_values)778 loss = mse_loss + quantile_loss779 780 return TimesFmOutputForPrediction(781 last_hidden_state=decoder_output.last_hidden_state,782 attentions=decoder_output.attentions if output_attentions else None,783 hidden_states=decoder_output.hidden_states if output_hidden_states else None,784 mean_predictions=mean_outputs,785 full_predictions=full_outputs,786 loss=loss,787 )788 789 @staticmethod790 def _timesfm_moving_average(arr: torch.Tensor, window_size: int) -> list[torch.Tensor]:791 """Calculates the moving average using PyTorch's convolution function."""792 # Pad with zeros to handle initial window positions793 arr_padded = F.pad(arr, (window_size - 1, 0), "constant", 0)794 # Create a convolution kernel795 kernel = torch.ones(window_size, dtype=arr.dtype, device=arr.device) / window_size796 # Apply convolution to calculate the moving average797 smoothed_arr = F.conv1d(arr_padded.view(1, 1, -1), kernel.view(1, 1, -1)).squeeze()798 return [smoothed_arr, arr - smoothed_arr]799 800 801__all__ = ["TimesFmModelForPrediction", "TimesFmPreTrainedModel", "TimesFmModel"]802 