KitsuVp commited on
Commit
7dcc293
·
verified ·
1 Parent(s): 630571d

Update modeling_neollm.py

Browse files
Files changed (1) hide show
  1. modeling_neollm.py +642 -54
modeling_neollm.py CHANGED
@@ -4946,9 +4946,13 @@ class SpellingBeeEmbedding(nn.Module):
4946
 
4947
  e_bee(t) = 0.5 * (e_tok(t) + e_chars(t))
4948
 
4949
- e_chars(t) = inv_sqrt_len(t) * Σ_{i=0}^{15} RoPE(e_byte[b_i], i)
 
4950
 
4951
- where inv_sqrt_len = 1/√|t| is precomputed per token type at setup time.
 
 
 
4952
 
4953
  Key design decisions vs. a naïve per-occurrence implementation:
4954
 
@@ -4966,10 +4970,11 @@ class SpellingBeeEmbedding(nn.Module):
4966
  dims) into a single kernel. Token-level e_chars is then a gather +
4967
  sum over this table, also fully static.
4968
 
4969
- 3. **Precomputed inv_sqrt_lens** — 1/√byte_len is computed once in
4970
- set_byte_table and stored as a persistent buffer. The per-forward
4971
- normalisation becomes a single elementwise multiply, with no sqrt or
4972
- division in the hot path.
 
4973
 
4974
  Compatible with both the standard embed_tokens path and the
4975
  LeviathanGenerator path.
@@ -4981,7 +4986,7 @@ class SpellingBeeEmbedding(nn.Module):
4981
 
4982
  **Setup: call ``set_byte_table(tokenizer)`` once after model init** (and
4983
  before any .to(device) / FP8 conversion) before training. The byte table
4984
- and inv_sqrt_lens are persistent buffers saved in checkpoints.
4985
 
4986
  References:
4987
  Rabe, Clymo & Dong (2026). "Spelling Bee Embeddings for Language
@@ -5009,19 +5014,6 @@ class SpellingBeeEmbedding(nn.Module):
5009
  torch.zeros(config.vocab_size, self.MAX_BYTES, dtype=torch.long),
5010
  persistent=True,
5011
  )
5012
- # inv_sqrt_lens [vocab_size]: precomputed 1/sqrt(byte_len) per token.
5013
- # Replaces the runtime sqrt+division of the naïve implementation.
5014
- self.register_buffer(
5015
- "inv_sqrt_lens",
5016
- torch.ones(config.vocab_size, dtype=torch.float),
5017
- persistent=True,
5018
- )
5019
-
5020
- # LayerNorm over character embeddings — mirrors the reference impl
5021
- # (character_norm=True by default in littletrainingloop). Runs in
5022
- # float32 for stability, applied at vocab level before the batch gather.
5023
- self.char_norm = nn.LayerNorm(d)
5024
-
5025
  def _load_from_state_dict(
5026
  self,
5027
  state_dict,
@@ -5056,7 +5048,7 @@ class SpellingBeeEmbedding(nn.Module):
5056
 
5057
  def set_byte_table(self, tokenizer) -> None:
5058
  """
5059
- Precompute the UTF-8 byte table and inv_sqrt_lens from a tokenizer.
5060
 
5061
  Must be called **once** after model instantiation and **before**
5062
  ``.to(device)`` / FP8 conversion so the buffers land on the correct
@@ -5064,30 +5056,36 @@ class SpellingBeeEmbedding(nn.Module):
5064
  be saved/restored from checkpoints automatically.
5065
 
5066
  Args:
5067
- tokenizer: Any HuggingFace tokenizer with
5068
- ``convert_ids_to_tokens(int) -> str | None``.
5069
  """
5070
  vocab_size = self.token_bytes.shape[0]
5071
  byte_ids = torch.zeros(vocab_size, self.MAX_BYTES, dtype=torch.long)
5072
- inv_sqrt = torch.ones(vocab_size, dtype=torch.float) # default 1/√1
5073
 
5074
  for token_id in range(vocab_size):
5075
- token_str = tokenizer.convert_ids_to_tokens(token_id)
5076
- if token_str is None:
5077
- continue
5078
- # Some tokenizers use a special space character (Ġ / ▁); encode
5079
- # directly to UTF-8 so byte values match raw text bytes.
5080
  try:
 
 
 
 
 
 
 
 
 
 
 
 
5081
  raw = token_str.encode("utf-8")
5082
  except Exception:
5083
  raw = b"\x00"
5084
  n = min(len(raw), self.MAX_BYTES)
5085
  for i in range(n):
5086
  byte_ids[token_id, i] = raw[i]
5087
- inv_sqrt[token_id] = 1.0 / math.sqrt(max(n, 1))
5088
-
5089
  self.token_bytes.copy_(byte_ids.to(self.token_bytes.device))
5090
- self.inv_sqrt_lens.copy_(inv_sqrt.to(self.inv_sqrt_lens.device))
5091
 
5092
  # ── Core helpers ──────────────────────────────────────────────────────────
5093
 
@@ -5160,18 +5158,7 @@ class SpellingBeeEmbedding(nn.Module):
5160
  torch.arange(
5161
  self.MAX_BYTES, device=rope_bytes.device
5162
  ), # [MAX_BYTES] — col index
5163
- ].sum(1) # [V, d]
5164
-
5165
- # ── Step 3: apply precomputed 1/√byte_len per vocab type ────────────
5166
- # No sqrt or division in the hot path — pure multiply.
5167
- e_chars_vocab = e_chars_vocab * self.inv_sqrt_lens.unsqueeze(-1) # [V, d]
5168
-
5169
- # ── Step 3b: LayerNorm over character embeddings (float32 for stability)
5170
- # Mirrors reference impl (character_norm=True). Ensures E[‖e_chars‖²]
5171
- # matches E[‖e_tok‖²] regardless of token byte-length distribution.
5172
- e_chars_vocab = self.char_norm(
5173
- e_chars_vocab.to(self.char_norm.weight.dtype)
5174
- ).to(token_embeds.dtype)
5175
 
5176
  # ── Step 4: gather only the tokens present in this batch ────────────
5177
  # This is the only B×S operation — a single embedding lookup.
@@ -5212,10 +5199,7 @@ class SpellingBeeEmbedding(nn.Module):
5212
  e_chars_vocab = rope_bytes[
5213
  self.token_bytes,
5214
  torch.arange(self.MAX_BYTES, device=rope_bytes.device),
5215
- ].sum(1) * self.inv_sqrt_lens.unsqueeze(-1) # [V, d]
5216
- e_chars_vocab = self.char_norm(
5217
- e_chars_vocab.to(self.char_norm.weight.dtype)
5218
- ).to(token_emb_weight.dtype)
5219
  return (token_emb_weight + e_chars_vocab) * 0.5
5220
 
5221
 
@@ -5413,9 +5397,10 @@ class NeoLLMPreTrainedModel(PreTrainedModel):
5413
  module.attn_res_query_mlp.data.zero_()
5414
 
5415
  elif isinstance(module, SpellingBeeEmbedding):
5416
- # byte_emb initialised identically to token embeddings: std=1/√d.
5417
- # Ensures E[‖e_byte‖²] ≈ 1 at init, matching etok, so the
5418
- # normalisation factor α = sqrt(byte_len) is calibrated from step 0.
 
5419
  d = module.byte_emb.embedding_dim
5420
  nn.init.normal_(module.byte_emb.weight, mean=0.0, std=1.0 / math.sqrt(d))
5421
 
@@ -6045,6 +6030,56 @@ def compute_nitp_loss(
6045
  return (per_token_loss * valid_weights).sum() / valid_count
6046
 
6047
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6048
  class NextLatDynamicsModel(nn.Module):
6049
  """
6050
  Latent dynamics model p_psi(h_t, x_{t+1}) from NextLat.
@@ -6099,6 +6134,321 @@ def _masked_smooth_l1(
6099
  return (loss * weights).sum() / weights.expand_as(loss).sum().clamp_min(1.0)
6100
 
6101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6102
  def _categorical_kl_loss(
6103
  teacher_logits: torch.Tensor,
6104
  student_logits: torch.Tensor,
@@ -6224,6 +6574,11 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
6224
  self.vocab_size = config.vocab_size
6225
  self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
6226
  self.nitp_projector = NITPProjector(config) if config.use_nitp else None
 
 
 
 
 
6227
  self.nextlat_dynamics = (
6228
  NextLatDynamicsModel(config) if config.use_nextlat else None
6229
  )
@@ -6252,6 +6607,20 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
6252
  self._last_ntp_loss = None
6253
  self._last_tweo_loss = None
6254
  self._last_nitp_loss = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6255
  self._last_nextlat_loss = None
6256
  self._last_nextlat_mse_loss = None
6257
  self._last_nextlat_kl_loss = None
@@ -6316,11 +6685,20 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
6316
  and labels is not None
6317
  and float(getattr(self.config, "tweo_loss_weight", 0.0)) > 0.0
6318
  )
 
 
 
 
 
 
6319
  nitp_enabled = (
6320
  bool(getattr(self.config, "use_nitp", False))
6321
  and self.nitp_projector is not None
6322
  and labels is not None
6323
- and float(getattr(self.config, "nitp_loss_weight", 0.0)) > 0.0
 
 
 
6324
  )
6325
  nextlat_enabled = (
6326
  bool(getattr(self.config, "use_nextlat", False))
@@ -6332,7 +6710,9 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
6332
  )
6333
  )
6334
  model_output_hidden_states = (
6335
- True if (nitp_enabled or nextlat_enabled) else output_hidden_states
 
 
6336
  )
6337
 
6338
  model_out = self.model(
@@ -6378,12 +6758,40 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
6378
  ntp_loss = None
6379
  tweo_loss = None
6380
  nitp_loss = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6381
  nextlat_loss = None
6382
  nextlat_mse_loss = None
6383
  nextlat_kl_loss = None
6384
  self._last_ntp_loss = None
6385
  self._last_tweo_loss = None
6386
  self._last_nitp_loss = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6387
  self._last_nextlat_loss = None
6388
  self._last_nextlat_mse_loss = None
6389
  self._last_nextlat_kl_loss = None
@@ -6445,7 +6853,112 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
6445
  projector=self.nitp_projector,
6446
  pad_token_id=self.config.pad_token_id,
6447
  )
6448
- loss = loss + self.config.nitp_loss_weight * nitp_loss
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6449
 
6450
  if nextlat_enabled:
6451
  if hidden_states_tuple is None or len(hidden_states_tuple) < 2:
@@ -6484,6 +6997,81 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
6484
  self._last_ntp_loss = ntp_loss.detach() if ntp_loss is not None else None
6485
  self._last_tweo_loss = tweo_loss.detach() if tweo_loss is not None else None
6486
  self._last_nitp_loss = nitp_loss.detach() if nitp_loss is not None else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6487
  self._last_nextlat_loss = (
6488
  nextlat_loss.detach() if nextlat_loss is not None else None
6489
  )
 
4946
 
4947
  e_bee(t) = 0.5 * (e_tok(t) + e_chars(t))
4948
 
4949
+ e_chars(t) = (1 / MAX_BYTES) * Σ_{i=0}^{15}
4950
+ RoPE(e_byte[b_i], i)
4951
 
4952
+ The active path uses the fixed 16-position mean from the reference
4953
+ implementation. A theoretical alternative is to mask padded positions
4954
+ and normalize only the valid bytes with ``1 / sqrt(|t|)``; that variant is
4955
+ documented in the configuration but is not active here.
4956
 
4957
  Key design decisions vs. a naïve per-occurrence implementation:
4958
 
 
4970
  dims) into a single kernel. Token-level e_chars is then a gather +
4971
  sum over this table, also fully static.
4972
 
4973
+ 3. **Fixed-length mean** — every token contributes exactly 16
4974
+ position-aware byte slots, including the learned null-byte padding,
4975
+ and the sum is divided by ``MAX_BYTES``. This keeps the active path
4976
+ faithful to the reference experiment and avoids a per-token scale
4977
+ factor.
4978
 
4979
  Compatible with both the standard embed_tokens path and the
4980
  LeviathanGenerator path.
 
4986
 
4987
  **Setup: call ``set_byte_table(tokenizer)`` once after model init** (and
4988
  before any .to(device) / FP8 conversion) before training. The byte table
4989
+ token_bytes is a persistent buffer saved in checkpoints.
4990
 
4991
  References:
4992
  Rabe, Clymo & Dong (2026). "Spelling Bee Embeddings for Language
 
5014
  torch.zeros(config.vocab_size, self.MAX_BYTES, dtype=torch.long),
5015
  persistent=True,
5016
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
5017
  def _load_from_state_dict(
5018
  self,
5019
  state_dict,
 
5048
 
5049
  def set_byte_table(self, tokenizer) -> None:
5050
  """
5051
+ Precompute the UTF-8 byte table from a tokenizer.
5052
 
5053
  Must be called **once** after model instantiation and **before**
5054
  ``.to(device)`` / FP8 conversion so the buffers land on the correct
 
5056
  be saved/restored from checkpoints automatically.
5057
 
5058
  Args:
5059
+ tokenizer: Any tokenizer with ``decode([token_id]) -> str``.
 
5060
  """
5061
  vocab_size = self.token_bytes.shape[0]
5062
  byte_ids = torch.zeros(vocab_size, self.MAX_BYTES, dtype=torch.long)
 
5063
 
5064
  for token_id in range(vocab_size):
5065
+ # Decode through the active tokenizer. convert_ids_to_tokens()
5066
+ # returns an internal BPE/SentencePiece symbol (for example
5067
+ # ``Ġapple`` or ``Ċ``), not the text surface (`` apple`` or ``\n``)
5068
+ # whose UTF-8 spelling Spelling Bee is meant to represent.
 
5069
  try:
5070
+ try:
5071
+ token_str = tokenizer.decode(
5072
+ [token_id],
5073
+ skip_special_tokens=False,
5074
+ clean_up_tokenization_spaces=False,
5075
+ )
5076
+ except TypeError:
5077
+ # Keep compatibility with tokenizer implementations that
5078
+ # do not expose clean_up_tokenization_spaces.
5079
+ token_str = tokenizer.decode(
5080
+ [token_id], skip_special_tokens=False
5081
+ )
5082
  raw = token_str.encode("utf-8")
5083
  except Exception:
5084
  raw = b"\x00"
5085
  n = min(len(raw), self.MAX_BYTES)
5086
  for i in range(n):
5087
  byte_ids[token_id, i] = raw[i]
 
 
5088
  self.token_bytes.copy_(byte_ids.to(self.token_bytes.device))
 
5089
 
5090
  # ── Core helpers ──────────────────────────────────────────────────────────
5091
 
 
5158
  torch.arange(
5159
  self.MAX_BYTES, device=rope_bytes.device
5160
  ), # [MAX_BYTES] — col index
5161
+ ].mean(1) # [V, d], fixed 16-position mean
 
 
 
 
 
 
 
 
 
 
 
5162
 
5163
  # ── Step 4: gather only the tokens present in this batch ────────────
5164
  # This is the only B×S operation — a single embedding lookup.
 
5199
  e_chars_vocab = rope_bytes[
5200
  self.token_bytes,
5201
  torch.arange(self.MAX_BYTES, device=rope_bytes.device),
5202
+ ].mean(1) # [V, d], fixed 16-position mean
 
 
 
5203
  return (token_emb_weight + e_chars_vocab) * 0.5
5204
 
5205
 
 
5397
  module.attn_res_query_mlp.data.zero_()
5398
 
5399
  elif isinstance(module, SpellingBeeEmbedding):
5400
+ # Keep the byte lookup on the same base scale as the model's
5401
+ # embedding initialization. The active SBE path uses the fixed
5402
+ # 16-position mean; the masked 1/sqrt(byte_len) alternative is
5403
+ # theoretical only and is documented separately.
5404
  d = module.byte_emb.embedding_dim
5405
  nn.init.normal_(module.byte_emb.weight, mean=0.0, std=1.0 / math.sqrt(d))
5406
 
 
6030
  return (per_token_loss * valid_weights).sum() / valid_count
6031
 
6032
 
6033
+ class NITPTemporalTransition(nn.Module):
6034
+ """
6035
+ NITP-native latent transition used by the temporal extension.
6036
+
6037
+ This module is intentionally independent from ``NextLatDynamicsModel``.
6038
+ It follows the existing NITP design language by using a bias-free SwiGLU
6039
+ update over the concatenation of the current final hidden state and the
6040
+ teacher-forced next-token embedding:
6041
+
6042
+ delta_t = W_down(SiLU(W_gate([h_t; e_{t+1}])) * W_up([h_t; e_{t+1}]))
6043
+ h_hat_{t+1} = h_t + delta_t
6044
+
6045
+ The residual update makes the learned object an update rule rather than a
6046
+ free reconstruction. It is a training-only auxiliary module and never
6047
+ materializes vocabulary logits.
6048
+
6049
+ Parameter count, excluding the already-existing NITP projector:
6050
+
6051
+ 2 * (2d * m) + (m * d) + 2d
6052
+
6053
+ where ``m = nitp_temporal_intermediate_size`` and the final ``2d`` term is
6054
+ the affine RMSNorm scale. With d=512 and m=1024 this is 2,622,464
6055
+ trainable parameters.
6056
+ """
6057
+
6058
+ def __init__(self, config: NeoLLMConfig):
6059
+ super().__init__()
6060
+ hidden_size = int(config.hidden_size)
6061
+ input_size = 2 * hidden_size
6062
+ intermediate_size = int(config.nitp_temporal_intermediate_size)
6063
+
6064
+ self.input_norm = nn.RMSNorm(input_size, eps=config.rms_norm_eps)
6065
+ self.gate_proj = nn.Linear(input_size, intermediate_size, bias=False)
6066
+ self.up_proj = nn.Linear(input_size, intermediate_size, bias=False)
6067
+ self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
6068
+
6069
+ def forward(
6070
+ self,
6071
+ current_states: torch.Tensor,
6072
+ next_token_embeds: torch.Tensor,
6073
+ ) -> torch.Tensor:
6074
+ transition_input = torch.cat([current_states, next_token_embeds], dim=-1)
6075
+ transition_input = self.input_norm(transition_input)
6076
+ delta = self.down_proj(
6077
+ F.silu(self.gate_proj(transition_input))
6078
+ * self.up_proj(transition_input)
6079
+ )
6080
+ return current_states + delta
6081
+
6082
+
6083
  class NextLatDynamicsModel(nn.Module):
6084
  """
6085
  Latent dynamics model p_psi(h_t, x_{t+1}) from NextLat.
 
6134
  return (loss * weights).sum() / weights.expand_as(loss).sum().clamp_min(1.0)
6135
 
6136
 
6137
+ def _valid_temporal_span_mask(
6138
+ valid_tokens: torch.Tensor,
6139
+ labels: torch.Tensor,
6140
+ span_length: int,
6141
+ eos_token_id: Optional[int],
6142
+ ) -> torch.Tensor:
6143
+ """
6144
+ Return a mask for contiguous temporal spans that stay inside one document.
6145
+
6146
+ A span includes the source state and every teacher-forced token consumed by
6147
+ the rollout. Padding / ignored labels invalidate the full span. EOS is
6148
+ permitted only at the final position, so no transition is trained across a
6149
+ packed-document boundary.
6150
+ """
6151
+ token_windows = valid_tokens.unfold(dimension=1, size=span_length, step=1)
6152
+ span_mask = token_windows.all(dim=-1)
6153
+ if eos_token_id is not None and span_length > 1:
6154
+ label_windows = labels.unfold(dimension=1, size=span_length, step=1)
6155
+ # The last token may itself be EOS; all earlier positions must remain
6156
+ # inside the same document.
6157
+ span_mask = span_mask & (label_windows[..., :-1] != eos_token_id).all(dim=-1)
6158
+ return span_mask
6159
+
6160
+
6161
+ def compute_nitp_temporal_objective(
6162
+ hidden_states: torch.Tensor,
6163
+ shallow_target_states: torch.Tensor,
6164
+ token_embeds: torch.Tensor,
6165
+ labels: torch.LongTensor,
6166
+ attention_mask: Optional[torch.Tensor],
6167
+ transition_model: NITPTemporalTransition,
6168
+ nitp_projector: NITPProjector,
6169
+ horizon: int,
6170
+ dynamics_weight: float,
6171
+ identification_weight: float,
6172
+ similarity_temperature: float,
6173
+ target_temperature: float,
6174
+ pad_token_id: Optional[int] = None,
6175
+ eos_token_id: Optional[int] = None,
6176
+ ) -> Tuple[
6177
+ torch.Tensor,
6178
+ torch.Tensor,
6179
+ torch.Tensor,
6180
+ torch.Tensor,
6181
+ torch.Tensor,
6182
+ torch.Tensor,
6183
+ torch.Tensor,
6184
+ torch.Tensor,
6185
+ torch.Tensor,
6186
+ torch.Tensor,
6187
+ torch.Tensor,
6188
+ torch.Tensor,
6189
+ ]:
6190
+ """
6191
+ NITP temporal transition and local temporal-identification objective.
6192
+
6193
+ The branch learns a teacher-forced rollout in final-hidden-state space:
6194
+
6195
+ h_hat_{t,0} = h_t
6196
+ h_hat_{t,k} = F_psi(h_hat_{t,k-1}, E[x_{t+k}]).
6197
+
6198
+ It contains two trainable losses:
6199
+
6200
+ 1. ``state_loss``: Smooth-L1 alignment of each rollout state with the
6201
+ stop-gradient real final state at the same offset.
6202
+ 2. ``identification_loss``: a symmetric listwise classification objective
6203
+ over the W x W cosine-similarity matrix. Each predicted step k must be
6204
+ identifiable as the real state at offset k. The target is temporally
6205
+ softened, so adjacent states may remain legitimately similar.
6206
+
6207
+ The objective never projects rollouts to vocabulary space. The existing
6208
+ NITP projector is used only for ``rollout_nitp_cosine``, a detached metric
6209
+ that checks whether predicted final states retain NITP's ability to predict
6210
+ the next shallow implicit token.
6211
+
6212
+ Returned scalars, in order:
6213
+ weighted_total, state_loss, identification_loss,
6214
+ temporal_top1_accuracy, mean_absolute_offset,
6215
+ diagonal_cosine, off_diagonal_cosine, identification_margin,
6216
+ last_step_state_loss, rollout_nitp_cosine,
6217
+ rollout_pair_cosine, valid_window_fraction.
6218
+ """
6219
+ horizon = int(horizon)
6220
+ seq_len = int(hidden_states.shape[1])
6221
+ max_horizon = max(min(horizon, seq_len - 1), 0)
6222
+ zero = hidden_states.new_zeros((), dtype=torch.float32)
6223
+ if max_horizon == 0:
6224
+ return (zero,) * 12
6225
+
6226
+ device = hidden_states.device
6227
+ labels_on_device = labels.to(device=device)
6228
+ if attention_mask is None:
6229
+ valid_tokens = torch.ones_like(labels_on_device, dtype=torch.bool)
6230
+ else:
6231
+ valid_tokens = attention_mask.to(device=device).bool()
6232
+ valid_tokens = valid_tokens & (labels_on_device != -100)
6233
+ if pad_token_id is not None:
6234
+ valid_tokens = valid_tokens & (labels_on_device != pad_token_id)
6235
+
6236
+ current_states = hidden_states
6237
+ shifted_token_embeds = token_embeds
6238
+ shifted_targets = hidden_states
6239
+ predicted_by_step = []
6240
+ state_loss_total = zero
6241
+ rollout_nitp_cosine_total = zero
6242
+ last_step_state_loss = zero
6243
+
6244
+ for step in range(max_horizon):
6245
+ # At step k, current_states contains either the real h_t (k=1) or the
6246
+ # recursively predicted h_hat_{t,k-1}. Every next token is the real
6247
+ # teacher-forced token, which isolates latent-transition quality from
6248
+ # token-sampling error.
6249
+ source_states = current_states[:, :-1, :]
6250
+ shifted_token_embeds = shifted_token_embeds[:, 1:, :]
6251
+ shifted_targets = shifted_targets[:, 1:, :]
6252
+ predicted_states = transition_model(source_states, shifted_token_embeds)
6253
+
6254
+ step_span = step + 2 # source + (step + 1) consumed future tokens
6255
+ step_mask = _valid_temporal_span_mask(
6256
+ valid_tokens=valid_tokens,
6257
+ labels=labels_on_device,
6258
+ span_length=step_span,
6259
+ eos_token_id=eos_token_id,
6260
+ )
6261
+ step_state_loss = _masked_smooth_l1(
6262
+ predicted=predicted_states,
6263
+ target=shifted_targets,
6264
+ mask=step_mask,
6265
+ )
6266
+ state_loss_total = state_loss_total + step_state_loss
6267
+ last_step_state_loss = step_state_loss
6268
+
6269
+ # NITP interaction metric only. It does not contribute gradients or
6270
+ # alter the objective. For k=1 this reproduces the ordinary NITP
6271
+ # alignment; later k measure whether recursively predicted states still
6272
+ # point toward the correct shallow implicit token.
6273
+ with torch.no_grad():
6274
+ shallow_target = shallow_target_states[:, step + 1 :, :]
6275
+ projected_shallow = nitp_projector(source_states)
6276
+ step_cosine = F.cosine_similarity(
6277
+ projected_shallow.float(),
6278
+ shallow_target.float(),
6279
+ dim=-1,
6280
+ eps=1e-8,
6281
+ )
6282
+ rollout_nitp_cosine_total = rollout_nitp_cosine_total + _masked_mean(
6283
+ step_cosine,
6284
+ step_mask,
6285
+ )
6286
+
6287
+ predicted_by_step.append(predicted_states)
6288
+ current_states = predicted_states
6289
+
6290
+ inv_horizon = 1.0 / float(max_horizon)
6291
+ state_loss = state_loss_total * inv_horizon
6292
+ rollout_nitp_cosine = rollout_nitp_cosine_total * inv_horizon
6293
+
6294
+ # All W rollout steps must share the same source positions to form the
6295
+ # local W x W identification matrix. The common number of starts is T-W.
6296
+ common_length = seq_len - max_horizon
6297
+ if common_length <= 0:
6298
+ weighted_total = float(dynamics_weight) * state_loss
6299
+ return (
6300
+ weighted_total,
6301
+ state_loss,
6302
+ zero,
6303
+ zero,
6304
+ zero,
6305
+ zero,
6306
+ zero,
6307
+ zero,
6308
+ last_step_state_loss,
6309
+ rollout_nitp_cosine,
6310
+ zero,
6311
+ zero,
6312
+ )
6313
+
6314
+ # Build only the small [B, T-W, W, W] matrix. Do not materialize a
6315
+ # [B, T-W, W, D] FP32 tensor: at the production shape B=64, T=512,
6316
+ # W=4, D=512 that unnecessary representation would consume hundreds of
6317
+ # MiB. Pairwise cosine calls keep only one [B, T-W, D] FP32 temporary.
6318
+ similarity_rows = []
6319
+ for predicted_states in predicted_by_step:
6320
+ predicted_common = predicted_states[:, :common_length, :]
6321
+ row = []
6322
+ for offset in range(1, max_horizon + 1):
6323
+ target_common = hidden_states[
6324
+ :, offset : offset + common_length, :
6325
+ ].detach()
6326
+ row.append(
6327
+ F.cosine_similarity(
6328
+ predicted_common.float(),
6329
+ target_common.float(),
6330
+ dim=-1,
6331
+ eps=1e-8,
6332
+ )
6333
+ )
6334
+ similarity_rows.append(torch.stack(row, dim=-1))
6335
+ similarity = torch.stack(
6336
+ similarity_rows,
6337
+ dim=-2,
6338
+ ) # [B, T-W, W_pred, W_real]
6339
+
6340
+ window_mask = _valid_temporal_span_mask(
6341
+ valid_tokens=valid_tokens,
6342
+ labels=labels_on_device,
6343
+ span_length=max_horizon + 1,
6344
+ eos_token_id=eos_token_id,
6345
+ )
6346
+ valid_window_fraction = window_mask.float().mean()
6347
+
6348
+ offsets = torch.arange(max_horizon, device=device)
6349
+ distance = (offsets[:, None] - offsets[None, :]).abs().float()
6350
+ target_scores = -distance / float(target_temperature)
6351
+ row_target = F.softmax(target_scores, dim=-1)
6352
+ col_target = F.softmax(target_scores, dim=0)
6353
+
6354
+ identification_logits = similarity / float(similarity_temperature)
6355
+ row_log_probs = F.log_softmax(identification_logits, dim=-1)
6356
+ col_log_probs = F.log_softmax(identification_logits, dim=-2)
6357
+ row_loss = -(row_target * row_log_probs).sum(dim=-1).mean(dim=-1)
6358
+ col_loss = -(col_target * col_log_probs).sum(dim=-2).mean(dim=-1)
6359
+ identification_loss = _masked_mean(
6360
+ 0.5 * (row_loss + col_loss),
6361
+ window_mask,
6362
+ )
6363
+
6364
+ row_prediction = similarity.argmax(dim=-1)
6365
+ expected_offsets = offsets.view(1, 1, -1)
6366
+ top1_per_window = (row_prediction == expected_offsets).float().mean(dim=-1)
6367
+ temporal_top1_accuracy = _masked_mean(top1_per_window, window_mask)
6368
+ mean_absolute_offset = _masked_mean(
6369
+ (row_prediction - expected_offsets).abs().float().mean(dim=-1),
6370
+ window_mask,
6371
+ )
6372
+
6373
+ diagonal = similarity.diagonal(dim1=-2, dim2=-1)
6374
+ diagonal_cosine = _masked_mean(diagonal.mean(dim=-1), window_mask)
6375
+
6376
+ if max_horizon > 1:
6377
+ identity = torch.eye(max_horizon, dtype=torch.bool, device=device)
6378
+ off_diagonal = similarity.masked_select(
6379
+ ~identity.view(1, 1, max_horizon, max_horizon)
6380
+ )
6381
+ off_diagonal = off_diagonal.view(
6382
+ similarity.shape[0],
6383
+ similarity.shape[1],
6384
+ max_horizon * (max_horizon - 1),
6385
+ )
6386
+ off_diagonal_cosine = _masked_mean(
6387
+ off_diagonal.mean(dim=-1),
6388
+ window_mask,
6389
+ )
6390
+
6391
+ hard_negative = similarity.masked_fill(
6392
+ identity.view(1, 1, max_horizon, max_horizon),
6393
+ torch.finfo(similarity.dtype).min,
6394
+ ).max(dim=-1).values
6395
+ identification_margin = _masked_mean(
6396
+ (diagonal - hard_negative).mean(dim=-1),
6397
+ window_mask,
6398
+ )
6399
+
6400
+ predicted_pair_rows = []
6401
+ for predicted_left in predicted_by_step:
6402
+ predicted_left = predicted_left[:, :common_length, :]
6403
+ pair_row = []
6404
+ for predicted_right in predicted_by_step:
6405
+ predicted_right = predicted_right[:, :common_length, :]
6406
+ pair_row.append(
6407
+ F.cosine_similarity(
6408
+ predicted_left.float(),
6409
+ predicted_right.float(),
6410
+ dim=-1,
6411
+ eps=1e-8,
6412
+ )
6413
+ )
6414
+ predicted_pair_rows.append(torch.stack(pair_row, dim=-1))
6415
+ predicted_pair_similarity = torch.stack(predicted_pair_rows, dim=-2)
6416
+ rollout_off_diagonal = predicted_pair_similarity.masked_select(
6417
+ ~identity.view(1, 1, max_horizon, max_horizon)
6418
+ ).view(
6419
+ similarity.shape[0],
6420
+ similarity.shape[1],
6421
+ max_horizon * (max_horizon - 1),
6422
+ )
6423
+ rollout_pair_cosine = _masked_mean(
6424
+ rollout_off_diagonal.mean(dim=-1),
6425
+ window_mask,
6426
+ )
6427
+ else:
6428
+ off_diagonal_cosine = zero
6429
+ identification_margin = zero
6430
+ rollout_pair_cosine = zero
6431
+
6432
+ weighted_total = (
6433
+ float(dynamics_weight) * state_loss
6434
+ + float(identification_weight) * identification_loss
6435
+ )
6436
+ return (
6437
+ weighted_total,
6438
+ state_loss,
6439
+ identification_loss,
6440
+ temporal_top1_accuracy,
6441
+ mean_absolute_offset,
6442
+ diagonal_cosine,
6443
+ off_diagonal_cosine,
6444
+ identification_margin,
6445
+ last_step_state_loss,
6446
+ rollout_nitp_cosine,
6447
+ rollout_pair_cosine,
6448
+ valid_window_fraction,
6449
+ )
6450
+
6451
+
6452
  def _categorical_kl_loss(
6453
  teacher_logits: torch.Tensor,
6454
  student_logits: torch.Tensor,
 
6574
  self.vocab_size = config.vocab_size
6575
  self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
6576
  self.nitp_projector = NITPProjector(config) if config.use_nitp else None
6577
+ self.nitp_temporal_transition = (
6578
+ NITPTemporalTransition(config)
6579
+ if bool(getattr(config, "use_nitp_temporal", False))
6580
+ else None
6581
+ )
6582
  self.nextlat_dynamics = (
6583
  NextLatDynamicsModel(config) if config.use_nextlat else None
6584
  )
 
6607
  self._last_ntp_loss = None
6608
  self._last_tweo_loss = None
6609
  self._last_nitp_loss = None
6610
+ self._last_nitp_temporal_loss = None
6611
+ self._last_nitp_temporal_state_loss = None
6612
+ self._last_nitp_temporal_identification_loss = None
6613
+ self._last_nitp_temporal_top1_accuracy = None
6614
+ self._last_nitp_temporal_mean_absolute_offset = None
6615
+ self._last_nitp_temporal_diagonal_cosine = None
6616
+ self._last_nitp_temporal_off_diagonal_cosine = None
6617
+ self._last_nitp_temporal_identification_margin = None
6618
+ self._last_nitp_temporal_last_step_state_loss = None
6619
+ self._last_nitp_temporal_rollout_nitp_cosine = None
6620
+ self._last_nitp_temporal_rollout_pair_cosine = None
6621
+ self._last_nitp_temporal_valid_window_fraction = None
6622
+ self._last_nitp_temporal_to_nitp_ratio = None
6623
+ self._last_nitp_temporal_loss_applied = None
6624
  self._last_nextlat_loss = None
6625
  self._last_nextlat_mse_loss = None
6626
  self._last_nextlat_kl_loss = None
 
6685
  and labels is not None
6686
  and float(getattr(self.config, "tweo_loss_weight", 0.0)) > 0.0
6687
  )
6688
+ nitp_temporal_enabled = (
6689
+ bool(getattr(self.config, "use_nitp_temporal", False))
6690
+ and self.nitp_temporal_transition is not None
6691
+ and self.nitp_projector is not None
6692
+ and labels is not None
6693
+ )
6694
  nitp_enabled = (
6695
  bool(getattr(self.config, "use_nitp", False))
6696
  and self.nitp_projector is not None
6697
  and labels is not None
6698
+ and (
6699
+ float(getattr(self.config, "nitp_loss_weight", 0.0)) > 0.0
6700
+ or nitp_temporal_enabled
6701
+ )
6702
  )
6703
  nextlat_enabled = (
6704
  bool(getattr(self.config, "use_nextlat", False))
 
6710
  )
6711
  )
6712
  model_output_hidden_states = (
6713
+ True
6714
+ if (nitp_enabled or nitp_temporal_enabled or nextlat_enabled)
6715
+ else output_hidden_states
6716
  )
6717
 
6718
  model_out = self.model(
 
6758
  ntp_loss = None
6759
  tweo_loss = None
6760
  nitp_loss = None
6761
+ nitp_temporal_loss = None
6762
+ nitp_temporal_state_loss = None
6763
+ nitp_temporal_identification_loss = None
6764
+ nitp_temporal_top1_accuracy = None
6765
+ nitp_temporal_mean_absolute_offset = None
6766
+ nitp_temporal_diagonal_cosine = None
6767
+ nitp_temporal_off_diagonal_cosine = None
6768
+ nitp_temporal_identification_margin = None
6769
+ nitp_temporal_last_step_state_loss = None
6770
+ nitp_temporal_rollout_nitp_cosine = None
6771
+ nitp_temporal_rollout_pair_cosine = None
6772
+ nitp_temporal_valid_window_fraction = None
6773
+ nitp_temporal_to_nitp_ratio = None
6774
+ nitp_target_hidden_states = None
6775
  nextlat_loss = None
6776
  nextlat_mse_loss = None
6777
  nextlat_kl_loss = None
6778
  self._last_ntp_loss = None
6779
  self._last_tweo_loss = None
6780
  self._last_nitp_loss = None
6781
+ self._last_nitp_temporal_loss = None
6782
+ self._last_nitp_temporal_state_loss = None
6783
+ self._last_nitp_temporal_identification_loss = None
6784
+ self._last_nitp_temporal_top1_accuracy = None
6785
+ self._last_nitp_temporal_mean_absolute_offset = None
6786
+ self._last_nitp_temporal_diagonal_cosine = None
6787
+ self._last_nitp_temporal_off_diagonal_cosine = None
6788
+ self._last_nitp_temporal_identification_margin = None
6789
+ self._last_nitp_temporal_last_step_state_loss = None
6790
+ self._last_nitp_temporal_rollout_nitp_cosine = None
6791
+ self._last_nitp_temporal_rollout_pair_cosine = None
6792
+ self._last_nitp_temporal_valid_window_fraction = None
6793
+ self._last_nitp_temporal_to_nitp_ratio = None
6794
+ self._last_nitp_temporal_loss_applied = None
6795
  self._last_nextlat_loss = None
6796
  self._last_nextlat_mse_loss = None
6797
  self._last_nextlat_kl_loss = None
 
6853
  projector=self.nitp_projector,
6854
  pad_token_id=self.config.pad_token_id,
6855
  )
6856
+ nitp_target_hidden_states = hidden_states_tuple[target_layer]
6857
+ if float(self.config.nitp_loss_weight) > 0.0:
6858
+ loss = loss + self.config.nitp_loss_weight * nitp_loss
6859
+
6860
+ if nitp_temporal_enabled:
6861
+ if hidden_states_tuple is None or len(hidden_states_tuple) < 2:
6862
+ raise ValueError(
6863
+ "`use_nitp_temporal=True` requires backbone hidden "
6864
+ "states. NeoLLMForCausalLM.forward enables them "
6865
+ "automatically while labels are present."
6866
+ )
6867
+ if nitp_target_hidden_states is None:
6868
+ target_layer = int(getattr(self.config, "nitp_target_layer", 1))
6869
+ if len(hidden_states_tuple) <= target_layer:
6870
+ raise ValueError(
6871
+ "`use_nitp_temporal=True` requires the NITP shallow "
6872
+ f"target layer {target_layer}."
6873
+ )
6874
+ nitp_target_hidden_states = hidden_states_tuple[target_layer]
6875
+
6876
+ token_embeds = hidden_states_tuple[0]
6877
+ temporal_apply_loss = bool(
6878
+ getattr(self.config, "nitp_temporal_apply_loss", True)
6879
+ )
6880
+ eos_token_id = (
6881
+ self.config.eos_token_id[0]
6882
+ if isinstance(self.config.eos_token_id, (list, tuple))
6883
+ and self.config.eos_token_id
6884
+ else self.config.eos_token_id
6885
+ )
6886
+ if temporal_apply_loss:
6887
+ temporal_results = compute_nitp_temporal_objective(
6888
+ hidden_states=hidden_states,
6889
+ shallow_target_states=nitp_target_hidden_states,
6890
+ token_embeds=token_embeds,
6891
+ labels=labels,
6892
+ attention_mask=attention_mask,
6893
+ transition_model=self.nitp_temporal_transition,
6894
+ nitp_projector=self.nitp_projector,
6895
+ horizon=self.config.nitp_temporal_horizon,
6896
+ dynamics_weight=self.config.nitp_temporal_dynamics_weight,
6897
+ identification_weight=(
6898
+ self.config.nitp_temporal_identification_weight
6899
+ ),
6900
+ similarity_temperature=(
6901
+ self.config.nitp_temporal_similarity_temperature
6902
+ ),
6903
+ target_temperature=(
6904
+ self.config.nitp_temporal_target_temperature
6905
+ ),
6906
+ pad_token_id=self.config.pad_token_id,
6907
+ eos_token_id=eos_token_id,
6908
+ )
6909
+ else:
6910
+ # Monitoring-only mode: the branch is instantiated (and
6911
+ # therefore contributes parameters) but its graph is not
6912
+ # retained and its objective is not added to training.
6913
+ with torch.no_grad():
6914
+ temporal_results = compute_nitp_temporal_objective(
6915
+ hidden_states=hidden_states,
6916
+ shallow_target_states=nitp_target_hidden_states,
6917
+ token_embeds=token_embeds,
6918
+ labels=labels,
6919
+ attention_mask=attention_mask,
6920
+ transition_model=self.nitp_temporal_transition,
6921
+ nitp_projector=self.nitp_projector,
6922
+ horizon=self.config.nitp_temporal_horizon,
6923
+ dynamics_weight=(
6924
+ self.config.nitp_temporal_dynamics_weight
6925
+ ),
6926
+ identification_weight=(
6927
+ self.config.nitp_temporal_identification_weight
6928
+ ),
6929
+ similarity_temperature=(
6930
+ self.config.nitp_temporal_similarity_temperature
6931
+ ),
6932
+ target_temperature=(
6933
+ self.config.nitp_temporal_target_temperature
6934
+ ),
6935
+ pad_token_id=self.config.pad_token_id,
6936
+ eos_token_id=eos_token_id,
6937
+ )
6938
+
6939
+ (
6940
+ nitp_temporal_loss,
6941
+ nitp_temporal_state_loss,
6942
+ nitp_temporal_identification_loss,
6943
+ nitp_temporal_top1_accuracy,
6944
+ nitp_temporal_mean_absolute_offset,
6945
+ nitp_temporal_diagonal_cosine,
6946
+ nitp_temporal_off_diagonal_cosine,
6947
+ nitp_temporal_identification_margin,
6948
+ nitp_temporal_last_step_state_loss,
6949
+ nitp_temporal_rollout_nitp_cosine,
6950
+ nitp_temporal_rollout_pair_cosine,
6951
+ nitp_temporal_valid_window_fraction,
6952
+ ) = temporal_results
6953
+
6954
+ if temporal_apply_loss:
6955
+ loss = loss + nitp_temporal_loss
6956
+
6957
+ if nitp_loss is not None:
6958
+ nitp_temporal_to_nitp_ratio = (
6959
+ nitp_temporal_loss.detach().float()
6960
+ / nitp_loss.detach().float().clamp_min(1e-8)
6961
+ )
6962
 
6963
  if nextlat_enabled:
6964
  if hidden_states_tuple is None or len(hidden_states_tuple) < 2:
 
6997
  self._last_ntp_loss = ntp_loss.detach() if ntp_loss is not None else None
6998
  self._last_tweo_loss = tweo_loss.detach() if tweo_loss is not None else None
6999
  self._last_nitp_loss = nitp_loss.detach() if nitp_loss is not None else None
7000
+ self._last_nitp_temporal_loss = (
7001
+ nitp_temporal_loss.detach()
7002
+ if nitp_temporal_loss is not None
7003
+ else None
7004
+ )
7005
+ self._last_nitp_temporal_state_loss = (
7006
+ nitp_temporal_state_loss.detach()
7007
+ if nitp_temporal_state_loss is not None
7008
+ else None
7009
+ )
7010
+ self._last_nitp_temporal_identification_loss = (
7011
+ nitp_temporal_identification_loss.detach()
7012
+ if nitp_temporal_identification_loss is not None
7013
+ else None
7014
+ )
7015
+ self._last_nitp_temporal_top1_accuracy = (
7016
+ nitp_temporal_top1_accuracy.detach()
7017
+ if nitp_temporal_top1_accuracy is not None
7018
+ else None
7019
+ )
7020
+ self._last_nitp_temporal_mean_absolute_offset = (
7021
+ nitp_temporal_mean_absolute_offset.detach()
7022
+ if nitp_temporal_mean_absolute_offset is not None
7023
+ else None
7024
+ )
7025
+ self._last_nitp_temporal_diagonal_cosine = (
7026
+ nitp_temporal_diagonal_cosine.detach()
7027
+ if nitp_temporal_diagonal_cosine is not None
7028
+ else None
7029
+ )
7030
+ self._last_nitp_temporal_off_diagonal_cosine = (
7031
+ nitp_temporal_off_diagonal_cosine.detach()
7032
+ if nitp_temporal_off_diagonal_cosine is not None
7033
+ else None
7034
+ )
7035
+ self._last_nitp_temporal_identification_margin = (
7036
+ nitp_temporal_identification_margin.detach()
7037
+ if nitp_temporal_identification_margin is not None
7038
+ else None
7039
+ )
7040
+ self._last_nitp_temporal_last_step_state_loss = (
7041
+ nitp_temporal_last_step_state_loss.detach()
7042
+ if nitp_temporal_last_step_state_loss is not None
7043
+ else None
7044
+ )
7045
+ self._last_nitp_temporal_rollout_nitp_cosine = (
7046
+ nitp_temporal_rollout_nitp_cosine.detach()
7047
+ if nitp_temporal_rollout_nitp_cosine is not None
7048
+ else None
7049
+ )
7050
+ self._last_nitp_temporal_rollout_pair_cosine = (
7051
+ nitp_temporal_rollout_pair_cosine.detach()
7052
+ if nitp_temporal_rollout_pair_cosine is not None
7053
+ else None
7054
+ )
7055
+ self._last_nitp_temporal_valid_window_fraction = (
7056
+ nitp_temporal_valid_window_fraction.detach()
7057
+ if nitp_temporal_valid_window_fraction is not None
7058
+ else None
7059
+ )
7060
+ self._last_nitp_temporal_to_nitp_ratio = (
7061
+ nitp_temporal_to_nitp_ratio.detach()
7062
+ if nitp_temporal_to_nitp_ratio is not None
7063
+ else None
7064
+ )
7065
+ self._last_nitp_temporal_loss_applied = (
7066
+ hidden_states.new_tensor(
7067
+ 1.0
7068
+ if bool(getattr(self.config, "nitp_temporal_apply_loss", True))
7069
+ else 0.0,
7070
+ dtype=torch.float32,
7071
+ )
7072
+ if nitp_temporal_enabled
7073
+ else None
7074
+ )
7075
  self._last_nextlat_loss = (
7076
  nextlat_loss.detach() if nextlat_loss is not None else None
7077
  )