KitsuVp commited on
Commit
c172d52
·
verified ·
1 Parent(s): 30ee5db

Upload final NeoLLM checkpoint and inference configuration

Browse files
config.json CHANGED
@@ -93,7 +93,7 @@
93
  "stack_memory_cache_size": 2048,
94
  "stack_slots": 16,
95
  "tie_word_embeddings": false,
96
- "transformers_version": "5.13.1",
97
  "tweo_eps": 1e-06,
98
  "tweo_loss_weight": 0.01,
99
  "tweo_power": 4.0,
 
93
  "stack_memory_cache_size": 2048,
94
  "stack_slots": 16,
95
  "tie_word_embeddings": false,
96
+ "transformers_version": "5.14.1",
97
  "tweo_eps": 1e-06,
98
  "tweo_loss_weight": 0.01,
99
  "tweo_power": 4.0,
configuration_neollm.py CHANGED
@@ -575,35 +575,52 @@ class NeoLLMConfig(PretrainedConfig):
575
  Numerical stability epsilon added to the TWEO denominator.
576
 
577
  use_nitp_temporal (:obj:`bool`, *optional*, defaults to ``False``):
578
- Instantiate the NITP temporal-transition extension. The module is
579
- a separate bias-free, context-conditioned multiplicative residual
580
- updater and does not reuse the existing ``NextLatDynamicsModel``.
581
- The final state proposes an update while the teacher-forced token
582
- embedding only gates that contextual proposal, so ``F(0, e)=0``.
583
- It predicts :math:`\hat h_{t+1}^L`, may be composed for
584
- ``nitp_temporal_horizon`` local rollout steps, and never materializes
585
- an additional vocabulary projection.
 
 
 
 
 
 
586
  nitp_temporal_apply_loss (:obj:`bool`, *optional*, defaults to ``True``):
587
  When ``True``, add the weighted temporal state and identification
588
  losses to the model loss. When ``False``, keep the transition
589
  parameters instantiated and compute all temporal metrics under
590
  ``torch.no_grad()``, allowing a monitoring-only NTP+NITP baseline.
591
  nitp_temporal_horizon (:obj:`int`, *optional*, defaults to ``4``):
592
- Number :math:`W` of teacher-forced latent rollout steps.
593
  nitp_temporal_dynamics_weight (:obj:`float`, *optional*, defaults to ``1.0``):
594
  Weight applied to the average Smooth-L1 alignment between each
595
  predicted final state and the stop-gradient real final state at
596
  the same temporal offset.
597
  nitp_temporal_identification_weight (:obj:`float`, *optional*, defaults to ``0.1``):
598
  Weight applied to the symmetric listwise temporal-identification
599
- objective over the local :math:`W\times W` cosine matrix.
 
 
 
 
 
 
 
 
 
 
 
600
  nitp_temporal_intermediate_size (:obj:`int`, *optional*):
601
- Intermediate width of the context proposal and token modulation
602
  paths. ``None`` resolves to ``2 * hidden_size``. At
603
  ``hidden_size=512`` this branch adds ``2,098,176`` parameters, in
604
  addition to the NITP projector.
605
  nitp_temporal_similarity_temperature (:obj:`float`, *optional*, defaults to ``0.1``):
606
- Temperature applied to predicted cosine similarities before the
607
  listwise temporal-identification softmax.
608
  nitp_temporal_target_temperature (:obj:`float`, *optional*, defaults to ``0.5``):
609
  Temperature controlling temporal label smoothing. Smaller values
@@ -827,9 +844,10 @@ class NeoLLMConfig(PretrainedConfig):
827
  nitp_projector_intermediate_size=None,
828
  # ── NITP temporal transition / identification extension ──────────
829
  # This is deliberately a NITP-side module, not the existing NextLat
830
- # implementation. It learns a teacher-forced latent update
831
- # (h_t^L, E[x_{t+1}]) -> h_{t+1}^L
832
- # and a local temporal-identification objective over the rollout.
 
833
  # `nitp_temporal_apply_loss=False` keeps the module instantiated and
834
  # computes every diagnostic metric, but does not add its objective to
835
  # the model loss. This is the requested monitoring-only ablation.
 
575
  Numerical stability epsilon added to the TWEO denominator.
576
 
577
  use_nitp_temporal (:obj:`bool`, *optional*, defaults to ``False``):
578
+ Instantiate the causal NITP temporal-transition extension. The
579
+ module is a separate bias-free multiplicative residual updater and
580
+ does not reuse ``NextLatDynamicsModel``. At every rollout step it
581
+ conditions on the detached NITP prediction of the next shallow
582
+ state, :math:`\operatorname{sg}(P_{\mathrm{NITP}}(\hat h_t))`,
583
+ never on the real future token embedding. The condition uses the
584
+ centered signed gate :math:`\tanh(W_z\bar z)`, which can
585
+ attenuate, activate, or reverse contextual update features. With
586
+ ``W_z=0`` the update vanishes, so the transition cannot learn a
587
+ non-trivial condition-independent path. It also preserves
588
+ ``F(0, z)=0``. The branch predicts
589
+ :math:`\hat h_{t+1}^L`, may be composed for
590
+ ``nitp_temporal_horizon`` causal rollout steps, and never
591
+ materializes vocabulary logits.
592
  nitp_temporal_apply_loss (:obj:`bool`, *optional*, defaults to ``True``):
593
  When ``True``, add the weighted temporal state and identification
594
  losses to the model loss. When ``False``, keep the transition
595
  parameters instantiated and compute all temporal metrics under
596
  ``torch.no_grad()``, allowing a monitoring-only NTP+NITP baseline.
597
  nitp_temporal_horizon (:obj:`int`, *optional*, defaults to ``4``):
598
+ Number :math:`W` of autonomous NITP-conditioned latent rollout steps.
599
  nitp_temporal_dynamics_weight (:obj:`float`, *optional*, defaults to ``1.0``):
600
  Weight applied to the average Smooth-L1 alignment between each
601
  predicted final state and the stop-gradient real final state at
602
  the same temporal offset.
603
  nitp_temporal_identification_weight (:obj:`float`, *optional*, defaults to ``0.1``):
604
  Weight applied to the symmetric listwise temporal-identification
605
+ objective over a local :math:`W\times W` joint-signature matrix.
606
+ The score keeps two conceptual halves, geometry and frozen-weight
607
+ NITP content. Geometry is an equal-norm concatenation of direct
608
+ accumulated-displacement cosine :math:`A` and a relational angular
609
+ profile cosine :math:`R` computed against every real future in the
610
+ local window. The resulting fixed score is
611
+ :math:`S=\tfrac14 A+\tfrac14 R+\tfrac12 Z`. Thus a rollout that
612
+ makes every step parallel is penalized only when the target
613
+ trajectory itself has distinguishable angular structure. No
614
+ direct diversity penalty, extra module, batch negative, vocabulary
615
+ projection, permutation in the trainable loss, or tunable mixing
616
+ coefficient is introduced.
617
  nitp_temporal_intermediate_size (:obj:`int`, *optional*):
618
+ Intermediate width of the context proposal and detached NITP-condition
619
  paths. ``None`` resolves to ``2 * hidden_size``. At
620
  ``hidden_size=512`` this branch adds ``2,098,176`` parameters, in
621
  addition to the NITP projector.
622
  nitp_temporal_similarity_temperature (:obj:`float`, *optional*, defaults to ``0.1``):
623
+ Temperature applied to joint-signature similarities before the
624
  listwise temporal-identification softmax.
625
  nitp_temporal_target_temperature (:obj:`float`, *optional*, defaults to ``0.5``):
626
  Temperature controlling temporal label smoothing. Smaller values
 
844
  nitp_projector_intermediate_size=None,
845
  # ── NITP temporal transition / identification extension ──────────
846
  # This is deliberately a NITP-side module, not the existing NextLat
847
+ # implementation. It learns the causal latent update
848
+ # z_{t+1} = stopgrad(P_NITP(h_t^L))
849
+ # (h_t^L, z_{t+1}) -> h_{t+1}^L
850
+ # and recursively recomputes z from every predicted state.
851
  # `nitp_temporal_apply_loss=False` keeps the module instantiated and
852
  # computes every diagnostic metric, but does not add its objective to
853
  # the model loss. This is the requested monitoring-only ablation.
generation_config.json CHANGED
@@ -7,5 +7,5 @@
7
  "output_attentions": false,
8
  "output_hidden_states": false,
9
  "pad_token_id": 0,
10
- "transformers_version": "5.13.1"
11
  }
 
7
  "output_attentions": false,
8
  "output_hidden_states": false,
9
  "pad_token_id": 0,
10
+ "transformers_version": "5.14.1"
11
  }
model.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:d316c201f8bab02846009bb30491378d9f582846f2aaf97442982a5986b90740
3
- size 244921040
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e2d79a82ec12f9a950502bacacb5343dc6a3f16209e741fa6213d3195d9a0c81
3
+ size 244921048
modeling_neollm.py CHANGED
@@ -5230,6 +5230,15 @@ class NeoLLMPreTrainedModel(PreTrainedModel):
5230
  - W_res: normal(0, initializer_range)
5231
  - router: parent handles (normal init)
5232
  - scaler: ones (identity at init)
 
 
 
 
 
 
 
 
 
5233
  NeoLLMAttention (Affine-Scaled Attention):
5234
  - alpha_proj: normal(0, 0.02) — near-zero so linear_clipping(≈0) ≈ 0.5
5235
  at init, giving a mild ~0.5× scaling of softmax weights
@@ -5258,6 +5267,19 @@ class NeoLLMPreTrainedModel(PreTrainedModel):
5258
  def _init_weights(self, module):
5259
  super()._init_weights(module)
5260
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5261
  if isinstance(module, NeoLLMAttention):
5262
  if getattr(module, "use_fan_residual", False):
5263
  if hasattr(module, "lambda_1") and module.lambda_1 is not None:
@@ -6032,30 +6054,34 @@ def compute_nitp_loss(
6032
 
6033
  class NITPTemporalTransition(nn.Module):
6034
  """
6035
- Context-conditioned multiplicative transition for temporal NITP.
6036
 
6037
- This module is intentionally independent from ``NextLatDynamicsModel``.
6038
- The current final hidden state proposes the latent update, while the
6039
- teacher-forced next-token embedding can only modulate that contextual
6040
- proposal:
6041
 
6042
  h_bar = RMSNorm_h(h_t)
6043
- e_bar = RMSNorm_e(E(x_{t+1}))
6044
  u_h = SiLU(W_gate h_bar) * (W_up h_bar)
6045
- m_e = sigmoid(W_token e_bar)
6046
- delta_t = W_down(u_h * m_e)
6047
  h_hat_{t+1} = h_t + delta_t
6048
 
6049
- All projections are bias-free. Consequently, removing the context forces
6050
- the complete update to zero for every token embedding:
 
 
 
6051
 
6052
- F(0, e) = 0
6053
- dF(0, e) / de = 0
6054
 
6055
- The token therefore cannot reconstruct a latent state by itself; it only
6056
- selects and scales features made available by the current contextual state.
6057
- The module remains a training-only latent auxiliary and never materializes
6058
- vocabulary logits.
 
 
6059
 
6060
  Parameter count, excluding the already-existing NITP projector:
6061
 
@@ -6071,12 +6097,13 @@ class NITPTemporalTransition(nn.Module):
6071
  hidden_size = int(config.hidden_size)
6072
  intermediate_size = int(config.nitp_temporal_intermediate_size)
6073
 
6074
- # Separate normalizations preserve the semantic distinction between
6075
- # the contextual state and the teacher-forced token observation.
 
6076
  self.context_norm = nn.RMSNorm(hidden_size, eps=config.rms_norm_eps)
6077
- self.token_norm = nn.RMSNorm(hidden_size, eps=config.rms_norm_eps)
6078
 
6079
- # Context path: proposes the update features.
6080
  self.context_gate_proj = nn.Linear(
6081
  hidden_size, intermediate_size, bias=False
6082
  )
@@ -6084,10 +6111,10 @@ class NITPTemporalTransition(nn.Module):
6084
  hidden_size, intermediate_size, bias=False
6085
  )
6086
 
6087
- # Token path: only modulates contextual features. It has no additive
6088
- # route to the output, so a token embedding cannot create a latent state
6089
- # when the contextual proposal is zero.
6090
- self.token_modulation_proj = nn.Linear(
6091
  hidden_size, intermediate_size, bias=False
6092
  )
6093
  self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
@@ -6095,17 +6122,19 @@ class NITPTemporalTransition(nn.Module):
6095
  def forward(
6096
  self,
6097
  current_states: torch.Tensor,
6098
- next_token_embeds: torch.Tensor,
6099
  ) -> torch.Tensor:
6100
  context = self.context_norm(current_states)
6101
- token = self.token_norm(next_token_embeds)
6102
 
6103
  context_update = (
6104
  F.silu(self.context_gate_proj(context))
6105
  * self.context_up_proj(context)
6106
  )
6107
- token_modulation = torch.sigmoid(self.token_modulation_proj(token))
6108
- delta = self.down_proj(context_update * token_modulation)
 
 
6109
  return current_states + delta
6110
 
6111
 
@@ -6172,10 +6201,10 @@ def _valid_temporal_span_mask(
6172
  """
6173
  Return a mask for contiguous temporal spans that stay inside one document.
6174
 
6175
- A span includes the source state and every teacher-forced token consumed by
6176
- the rollout. Padding / ignored labels invalidate the full span. EOS is
6177
- permitted only at the final position, so no transition is trained across a
6178
- packed-document boundary.
6179
  """
6180
  token_windows = valid_tokens.unfold(dimension=1, size=span_length, step=1)
6181
  span_mask = token_windows.all(dim=-1)
@@ -6187,45 +6216,324 @@ def _valid_temporal_span_mask(
6187
  return span_mask
6188
 
6189
 
6190
- def _build_local_temporal_similarity(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6191
  predicted_by_step: list[torch.Tensor],
 
6192
  target_hidden_states: torch.Tensor,
 
 
6193
  common_length: int,
6194
- ) -> torch.Tensor:
 
 
 
 
 
6195
  """
6196
- Build the local ``W x W`` cosine matrix used by temporal diagnostics.
6197
-
6198
- Row ``k`` contains the cosine similarity between rollout state
6199
- ``h_hat_{t,k}`` and every real future state ``h_{t+j}``. Targets are
6200
- detached because all temporal targets are stop-gradient observations.
6201
- This helper is used only by the trained rollout diagnostics. The
6202
- context-specificity counterfactual intentionally needs only Smooth-L1 and
6203
- therefore avoids building a second W x W similarity matrix.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6204
  """
6205
- similarity_rows = []
 
 
 
 
6206
  for predicted_states in predicted_by_step:
6207
- predicted_common = predicted_states[:, :common_length, :]
 
 
6208
  row = []
6209
- for offset in range(1, len(predicted_by_step) + 1):
6210
- target_common = target_hidden_states[
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6211
  :, offset : offset + common_length, :
6212
  ].detach()
6213
  row.append(
6214
  F.cosine_similarity(
6215
- predicted_common.float(),
6216
- target_common.float(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6217
  dim=-1,
6218
- eps=1e-8,
6219
  )
6220
  )
6221
- similarity_rows.append(torch.stack(row, dim=-1))
6222
- return torch.stack(similarity_rows, dim=-2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6223
 
6224
 
6225
  def compute_nitp_temporal_objective(
6226
  hidden_states: torch.Tensor,
6227
  shallow_target_states: torch.Tensor,
6228
- token_embeds: torch.Tensor,
6229
  labels: torch.LongTensor,
6230
  attention_mask: Optional[torch.Tensor],
6231
  transition_model: NITPTemporalTransition,
@@ -6237,79 +6545,63 @@ def compute_nitp_temporal_objective(
6237
  target_temperature: float,
6238
  pad_token_id: Optional[int] = None,
6239
  eos_token_id: Optional[int] = None,
6240
- ) -> Tuple[
6241
- torch.Tensor,
6242
- torch.Tensor,
6243
- torch.Tensor,
6244
- torch.Tensor,
6245
- torch.Tensor,
6246
- torch.Tensor,
6247
- torch.Tensor,
6248
- torch.Tensor,
6249
- torch.Tensor,
6250
- torch.Tensor,
6251
- torch.Tensor,
6252
- torch.Tensor,
6253
- torch.Tensor,
6254
- ]:
6255
  """
6256
- NITP temporal transition and local temporal-identification objective.
6257
 
6258
- The branch learns a teacher-forced rollout in final-hidden-state space:
6259
 
6260
  h_hat_{t,0} = h_t
6261
- h_hat_{t,k} = F_psi(h_hat_{t,k-1}, E[x_{t+k}]).
6262
-
6263
- It contains two trainable losses:
6264
-
6265
- 1. ``state_loss``: Smooth-L1 alignment of each rollout state with the
6266
- stop-gradient real final state at the same offset.
6267
- 2. ``identification_loss``: a symmetric listwise classification objective
6268
- over the W x W cosine-similarity matrix. Each predicted step k must be
6269
- identifiable as the real state at offset k. The target is temporally
6270
- softened, so adjacent states may remain legitimately similar.
6271
-
6272
- The objective never projects rollouts to vocabulary space. The existing
6273
- NITP projector is used only for ``rollout_nitp_cosine``, a detached metric
6274
- that checks whether predicted final states retain NITP's ability to predict
6275
- the next shallow implicit token.
6276
-
6277
- The shortcut diagnostic is an evaluation-only, on-manifold context
6278
- counterfactual. For example ``i`` it keeps the exact teacher-forced future
6279
- tokens but replaces the initial state with a different example's state:
6280
-
6281
- pi(i) = (i + 1) mod B
6282
- h_tilde_{i,t,0} = stopgrad(h^L_{pi(i),t})
6283
- h_tilde_{i,t,k} = F_psi(
6284
- h_tilde_{i,t,k-1}, E[x_{i,t+k}]
6285
- ).
6286
-
6287
- Let L_full be ``state_loss`` and L_wrong the Smooth-L1 loss of that
6288
- wrong-context rollout against the original targets. The single logged
6289
- diagnostic is the bounded context-specificity score
6290
-
6291
- C_ctx = (L_wrong - L_full) / (L_wrong + L_full + eps).
6292
-
6293
- ``C_ctx`` near zero means that an arbitrary plausible context works almost
6294
- as well as the correct one; positive values mean the exact contextual state
6295
- matters; negative values mean the wrong context unexpectedly performs
6296
- better. The counterfactual runs under ``torch.no_grad()`` and does not
6297
- modify the training objective. A deterministic cyclic batch shift avoids
6298
- RNG, graph recompilation, and cross-rank communication.
6299
-
6300
- Returned scalars, in order:
6301
- weighted_total, state_loss, identification_loss,
6302
- temporal_top1_accuracy, mean_absolute_offset,
6303
- diagonal_cosine, off_diagonal_cosine, identification_margin,
6304
- last_step_state_loss, rollout_nitp_cosine,
6305
- rollout_pair_cosine, valid_window_fraction, context_specificity.
6306
  """
6307
  horizon = int(horizon)
6308
  seq_len = int(hidden_states.shape[1])
6309
  max_horizon = max(min(horizon, seq_len - 1), 0)
6310
  zero = hidden_states.new_zeros((), dtype=torch.float32)
6311
  if max_horizon == 0:
6312
- return (zero,) * 13
 
 
 
 
 
 
6313
 
6314
  device = hidden_states.device
6315
  labels_on_device = labels.to(device=device)
@@ -6322,69 +6614,167 @@ def compute_nitp_temporal_objective(
6322
  valid_tokens = valid_tokens & (labels_on_device != pad_token_id)
6323
 
6324
  current_states = hidden_states
6325
- shifted_token_embeds = token_embeds
6326
  shifted_targets = hidden_states
6327
- predicted_by_step = []
6328
- step_masks = []
 
 
 
 
 
 
 
 
6329
  state_loss_total = zero
6330
- rollout_nitp_cosine_total = zero
6331
- last_step_state_loss = zero
 
 
6332
 
6333
  for step in range(max_horizon):
6334
- # At step k, current_states contains either the real h_t (k=1) or the
6335
- # recursively predicted h_hat_{t,k-1}. Every next token is the real
6336
- # teacher-forced token, which isolates latent-transition quality from
6337
- # token-sampling error.
6338
  source_states = current_states[:, :-1, :]
6339
- shifted_token_embeds = shifted_token_embeds[:, 1:, :]
6340
  shifted_targets = shifted_targets[:, 1:, :]
6341
- predicted_states = transition_model(source_states, shifted_token_embeds)
 
 
 
 
 
 
 
6342
 
6343
- step_span = step + 2 # source + (step + 1) consumed future tokens
6344
  step_mask = _valid_temporal_span_mask(
6345
  valid_tokens=valid_tokens,
6346
  labels=labels_on_device,
6347
- span_length=step_span,
6348
  eos_token_id=eos_token_id,
6349
  )
6350
  step_masks.append(step_mask)
 
6351
  step_state_loss = _masked_smooth_l1(
6352
  predicted=predicted_states,
6353
  target=shifted_targets,
6354
  mask=step_mask,
6355
  )
6356
  state_loss_total = state_loss_total + step_state_loss
6357
- last_step_state_loss = step_state_loss
6358
 
6359
- # NITP interaction metric only. It does not contribute gradients or
6360
- # alter the objective. For k=1 this reproduces the ordinary NITP
6361
- # alignment; later k measure whether recursively predicted states still
6362
- # point toward the correct shallow implicit token.
6363
  with torch.no_grad():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6364
  shallow_target = shallow_target_states[:, step + 1 :, :]
6365
- projected_shallow = nitp_projector(source_states)
6366
- step_cosine = F.cosine_similarity(
6367
- projected_shallow.float(),
6368
  shallow_target.float(),
6369
  dim=-1,
6370
- eps=1e-8,
6371
  )
6372
- rollout_nitp_cosine_total = rollout_nitp_cosine_total + _masked_mean(
6373
- step_cosine,
6374
- step_mask,
6375
  )
6376
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6377
  predicted_by_step.append(predicted_states)
 
6378
  current_states = predicted_states
6379
 
6380
  inv_horizon = 1.0 / float(max_horizon)
6381
  state_loss = state_loss_total * inv_horizon
6382
- rollout_nitp_cosine = rollout_nitp_cosine_total * inv_horizon
 
 
 
 
 
 
 
 
 
 
 
 
6383
 
6384
- # All W rollout steps must share the same source positions to form the
6385
- # local W x W identification matrix. The common number of starts is T-W.
6386
  common_length = seq_len - max_horizon
6387
  if common_length <= 0:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6388
  weighted_total = float(dynamics_weight) * state_loss
6389
  return (
6390
  weighted_total,
@@ -6396,21 +6786,30 @@ def compute_nitp_temporal_objective(
6396
  zero,
6397
  zero,
6398
  last_step_state_loss,
 
6399
  rollout_nitp_cosine,
6400
  zero,
6401
  zero,
6402
  zero,
 
 
 
 
6403
  )
6404
 
6405
- # Build only the small [B, T-W, W, W] matrix. Do not materialize a
6406
- # [B, T-W, W, D] FP32 tensor: at the production shape B=64, T=512,
6407
- # W=4, D=512 that unnecessary representation would consume hundreds of
6408
- # MiB. Pairwise cosine calls keep only one [B, T-W, D] FP32 temporary.
6409
- similarity = _build_local_temporal_similarity(
 
6410
  predicted_by_step=predicted_by_step,
 
6411
  target_hidden_states=hidden_states,
 
 
6412
  common_length=common_length,
6413
- ) # [B, T-W, W_pred, W_real]
6414
 
6415
  window_mask = _valid_temporal_span_mask(
6416
  valid_tokens=valid_tokens,
@@ -6438,137 +6837,191 @@ def compute_nitp_temporal_objective(
6438
 
6439
  row_prediction = similarity.argmax(dim=-1)
6440
  expected_offsets = offsets.view(1, 1, -1)
6441
- top1_per_window = (row_prediction == expected_offsets).float().mean(dim=-1)
6442
- temporal_top1_accuracy = _masked_mean(top1_per_window, window_mask)
 
 
6443
  mean_absolute_offset = _masked_mean(
6444
  (row_prediction - expected_offsets).abs().float().mean(dim=-1),
6445
  window_mask,
6446
  )
6447
 
6448
- diagonal = similarity.diagonal(dim1=-2, dim2=-1)
6449
- diagonal_cosine = _masked_mean(diagonal.mean(dim=-1), window_mask)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6450
 
6451
- if max_horizon > 1:
6452
- identity = torch.eye(max_horizon, dtype=torch.bool, device=device)
6453
- off_diagonal = similarity.masked_select(
6454
- ~identity.view(1, 1, max_horizon, max_horizon)
6455
- )
6456
- off_diagonal = off_diagonal.view(
6457
- similarity.shape[0],
6458
- similarity.shape[1],
6459
- max_horizon * (max_horizon - 1),
 
 
 
 
 
 
6460
  )
6461
- off_diagonal_cosine = _masked_mean(
6462
- off_diagonal.mean(dim=-1),
6463
  window_mask,
6464
  )
6465
-
6466
- hard_negative = similarity.masked_fill(
6467
- identity.view(1, 1, max_horizon, max_horizon),
6468
- torch.finfo(similarity.dtype).min,
6469
- ).max(dim=-1).values
6470
- identification_margin = _masked_mean(
6471
- (diagonal - hard_negative).mean(dim=-1),
6472
  window_mask,
6473
  )
6474
-
6475
- predicted_pair_rows = []
6476
- for predicted_left in predicted_by_step:
6477
- predicted_left = predicted_left[:, :common_length, :]
6478
- pair_row = []
6479
- for predicted_right in predicted_by_step:
6480
- predicted_right = predicted_right[:, :common_length, :]
6481
- pair_row.append(
6482
- F.cosine_similarity(
6483
- predicted_left.float(),
6484
- predicted_right.float(),
6485
- dim=-1,
6486
- eps=1e-8,
6487
- )
6488
- )
6489
- predicted_pair_rows.append(torch.stack(pair_row, dim=-1))
6490
- predicted_pair_similarity = torch.stack(predicted_pair_rows, dim=-2)
6491
- rollout_off_diagonal = predicted_pair_similarity.masked_select(
6492
- ~identity.view(1, 1, max_horizon, max_horizon)
6493
- ).view(
6494
- similarity.shape[0],
6495
- similarity.shape[1],
6496
- max_horizon * (max_horizon - 1),
6497
- )
6498
- rollout_pair_cosine = _masked_mean(
6499
- rollout_off_diagonal.mean(dim=-1),
6500
  window_mask,
6501
  )
6502
- else:
6503
- off_diagonal_cosine = zero
6504
- identification_margin = zero
6505
- rollout_pair_cosine = zero
6506
-
6507
- # ------------------------------------------------------------------
6508
- # Context-specificity diagnostic (monitoring only, no gradients).
6509
- # ------------------------------------------------------------------
6510
- # The multiplicative transition already guarantees F(0, e) = 0, so a
6511
- # zero-context/token-only rollout is now true by construction and no longer
6512
- # diagnoses learned behavior. Instead, use an on-manifold but incorrect
6513
- # context from the next example in the local batch while retaining each
6514
- # example's exact teacher-forced future tokens:
6515
- #
6516
- # pi(i) = (i + 1) mod B
6517
- # h_tilde_{i,t,0} = stopgrad(h^L_{pi(i),t})
6518
- # h_tilde_{i,t,k} = F_psi(h_tilde_{i,t,k-1}, E[x_{i,t+k}]).
6519
- #
6520
- # With L_full = state_loss and L_wrong the counterfactual Smooth-L1 loss,
6521
- # report the bounded, scale-normalized score
6522
- #
6523
- # C_ctx = (L_wrong - L_full) / (L_wrong + L_full + eps).
6524
- #
6525
- # C_ctx ~= 0: any plausible context works almost equally well.
6526
- # C_ctx > 0: the exact context improves the transition.
6527
- # C_ctx < 0: the wrong context unexpectedly performs better.
6528
- #
6529
- # This replaces the three old token-only metrics at lower cost: it reuses
6530
- # one no-grad rollout but does not build a second W x W similarity matrix.
6531
- with torch.no_grad():
6532
- if int(hidden_states.shape[0]) > 1:
6533
- wrong_context_current = torch.roll(
6534
- hidden_states.detach(), shifts=1, dims=0
6535
- )
6536
- wrong_context_shifted_embeds = token_embeds.detach()
6537
- wrong_context_targets = hidden_states.detach()
6538
- wrong_context_state_loss_total = zero
6539
 
 
 
 
6540
  for step in range(max_horizon):
6541
- wrong_context_source = wrong_context_current[:, :-1, :]
6542
- wrong_context_shifted_embeds = (
6543
- wrong_context_shifted_embeds[:, 1:, :]
 
 
6544
  )
6545
- wrong_context_targets = wrong_context_targets[:, 1:, :]
6546
- wrong_context_predicted = transition_model(
6547
- wrong_context_source,
6548
- wrong_context_shifted_embeds,
6549
  )
6550
- wrong_context_state_loss_total = (
6551
- wrong_context_state_loss_total
6552
- + _masked_smooth_l1(
6553
- predicted=wrong_context_predicted,
6554
- target=wrong_context_targets,
6555
- mask=step_masks[step],
6556
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6557
  )
6558
- wrong_context_current = wrong_context_predicted
 
 
 
 
 
 
 
 
 
 
6559
 
6560
- wrong_context_state_loss = (
6561
- wrong_context_state_loss_total * inv_horizon
6562
- )
6563
- full_state_loss = state_loss.detach().float()
6564
- wrong_state_loss = wrong_context_state_loss.float()
6565
- context_specificity = (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6566
  (wrong_state_loss - full_state_loss)
6567
  / (wrong_state_loss + full_state_loss + 1.0e-8)
6568
  ).clamp(min=-1.0, max=1.0)
6569
  else:
6570
- # A distinct on-manifold context does not exist for batch size one.
6571
- context_specificity = zero
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6572
 
6573
  weighted_total = (
6574
  float(dynamics_weight) * state_loss
@@ -6584,10 +7037,15 @@ def compute_nitp_temporal_objective(
6584
  off_diagonal_cosine,
6585
  identification_margin,
6586
  last_step_state_loss,
 
6587
  rollout_nitp_cosine,
6588
  rollout_pair_cosine,
6589
  valid_window_fraction,
6590
- context_specificity,
 
 
 
 
6591
  )
6592
 
6593
 
@@ -6761,9 +7219,14 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
6761
  self._last_nitp_temporal_rollout_nitp_cosine = None
6762
  self._last_nitp_temporal_rollout_pair_cosine = None
6763
  self._last_nitp_temporal_valid_window_fraction = None
6764
- self._last_nitp_temporal_context_specificity = None
 
6765
  self._last_nitp_temporal_to_nitp_ratio = None
6766
  self._last_nitp_temporal_loss_applied = None
 
 
 
 
6767
  self._last_nextlat_loss = None
6768
  self._last_nextlat_mse_loss = None
6769
  self._last_nextlat_kl_loss = None
@@ -6913,7 +7376,12 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
6913
  nitp_temporal_rollout_nitp_cosine = None
6914
  nitp_temporal_rollout_pair_cosine = None
6915
  nitp_temporal_valid_window_fraction = None
6916
- nitp_temporal_context_specificity = None
 
 
 
 
 
6917
  nitp_temporal_to_nitp_ratio = None
6918
  nitp_target_hidden_states = None
6919
  nextlat_loss = None
@@ -6934,9 +7402,14 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
6934
  self._last_nitp_temporal_rollout_nitp_cosine = None
6935
  self._last_nitp_temporal_rollout_pair_cosine = None
6936
  self._last_nitp_temporal_valid_window_fraction = None
6937
- self._last_nitp_temporal_context_specificity = None
 
6938
  self._last_nitp_temporal_to_nitp_ratio = None
6939
  self._last_nitp_temporal_loss_applied = None
 
 
 
 
6940
  self._last_nextlat_loss = None
6941
  self._last_nextlat_mse_loss = None
6942
  self._last_nextlat_kl_loss = None
@@ -7018,7 +7491,6 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7018
  )
7019
  nitp_target_hidden_states = hidden_states_tuple[target_layer]
7020
 
7021
- token_embeds = hidden_states_tuple[0]
7022
  temporal_apply_loss = bool(
7023
  getattr(self.config, "nitp_temporal_apply_loss", True)
7024
  )
@@ -7032,7 +7504,6 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7032
  temporal_results = compute_nitp_temporal_objective(
7033
  hidden_states=hidden_states,
7034
  shallow_target_states=nitp_target_hidden_states,
7035
- token_embeds=token_embeds,
7036
  labels=labels,
7037
  attention_mask=attention_mask,
7038
  transition_model=self.nitp_temporal_transition,
@@ -7059,7 +7530,6 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7059
  temporal_results = compute_nitp_temporal_objective(
7060
  hidden_states=hidden_states,
7061
  shallow_target_states=nitp_target_hidden_states,
7062
- token_embeds=token_embeds,
7063
  labels=labels,
7064
  attention_mask=attention_mask,
7065
  transition_model=self.nitp_temporal_transition,
@@ -7091,10 +7561,15 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7091
  nitp_temporal_off_diagonal_cosine,
7092
  nitp_temporal_identification_margin,
7093
  nitp_temporal_last_step_state_loss,
 
7094
  nitp_temporal_rollout_nitp_cosine,
7095
  nitp_temporal_rollout_pair_cosine,
7096
  nitp_temporal_valid_window_fraction,
7097
- nitp_temporal_context_specificity,
 
 
 
 
7098
  ) = temporal_results
7099
 
7100
  if temporal_apply_loss:
@@ -7113,10 +7588,10 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7113
  "NeoLLMForCausalLM.forward enables them automatically "
7114
  "while labels are present."
7115
  )
 
 
 
7116
  token_embeds = hidden_states_tuple[0]
7117
- # NextLat uses final hidden states h_t and input embeddings
7118
- # x_{t+1}; p_psi is training-only and inference remains the
7119
- # standard autoregressive LM path.
7120
  nextlat_loss, nextlat_mse_loss, nextlat_kl_loss = compute_nextlat_loss(
7121
  hidden_states=hidden_states,
7122
  token_embeds=token_embeds,
@@ -7203,9 +7678,34 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7203
  if nitp_temporal_valid_window_fraction is not None
7204
  else None
7205
  )
7206
- self._last_nitp_temporal_context_specificity = (
7207
- nitp_temporal_context_specificity.detach()
7208
- if nitp_temporal_context_specificity is not None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7209
  else None
7210
  )
7211
  self._last_nitp_temporal_to_nitp_ratio = (
 
5230
  - W_res: normal(0, initializer_range)
5231
  - router: parent handles (normal init)
5232
  - scaler: ones (identity at init)
5233
+ NITPTemporalTransition:
5234
+ - context_gate_proj/context_up_proj/condition_modulation_proj:
5235
+ Xavier-uniform so both the contextual proposal and the
5236
+ detached NITP condition have non-degenerate variance at
5237
+ step 0.
5238
+ - down_proj: normal(0, 0.01) — keeps the residual transition close
5239
+ to identity initially without blocking gradients into
5240
+ the centered condition gate.
5241
+ - context_norm/condition_norm: weight=1.
5242
  NeoLLMAttention (Affine-Scaled Attention):
5243
  - alpha_proj: normal(0, 0.02) — near-zero so linear_clipping(≈0) ≈ 0.5
5244
  at init, giving a mild ~0.5× scaling of softmax weights
 
5267
  def _init_weights(self, module):
5268
  super()._init_weights(module)
5269
 
5270
+ if isinstance(module, NITPTemporalTransition):
5271
+ # A centered tanh gate has no constant 0.5 path. Xavier
5272
+ # initialization gives the detached NITP condition measurable
5273
+ # variation from step 0, while a small non-zero down projection
5274
+ # keeps the full residual transition close to identity without
5275
+ # suppressing gradients into the condition path.
5276
+ nn.init.xavier_uniform_(module.context_gate_proj.weight)
5277
+ nn.init.xavier_uniform_(module.context_up_proj.weight)
5278
+ nn.init.xavier_uniform_(module.condition_modulation_proj.weight)
5279
+ nn.init.normal_(module.down_proj.weight, mean=0.0, std=0.01)
5280
+ module.context_norm.weight.data.fill_(1.0)
5281
+ module.condition_norm.weight.data.fill_(1.0)
5282
+
5283
  if isinstance(module, NeoLLMAttention):
5284
  if getattr(module, "use_fan_residual", False):
5285
  if hasattr(module, "lambda_1") and module.lambda_1 is not None:
 
6054
 
6055
  class NITPTemporalTransition(nn.Module):
6056
  """
6057
+ Causal NITP-conditioned multiplicative transition.
6058
 
6059
+ This module remains independent from ``NextLatDynamicsModel``. The current
6060
+ final hidden state proposes a latent update, while the detached NITP
6061
+ prediction of the next shallow state modulates that proposal:
 
6062
 
6063
  h_bar = RMSNorm_h(h_t)
6064
+ z_bar = RMSNorm_z(stopgrad(P_NITP(h_t)))
6065
  u_h = SiLU(W_gate h_bar) * (W_up h_bar)
6066
+ s_z = tanh(W_condition z_bar)
6067
+ delta_t = W_down(u_h * s_z)
6068
  h_hat_{t+1} = h_t + delta_t
6069
 
6070
+ The centered signed gate satisfies ``s_z in (-1, 1)``. Unlike a
6071
+ positive sigmoid gate, it can attenuate, activate, or reverse an
6072
+ individual contextual feature. More importantly, ``W_condition=0``
6073
+ now implies ``s_z=0`` and therefore ``T(h, z)=h``: the transition can
6074
+ no longer learn a non-trivial update while ignoring the NITP condition.
6075
 
6076
+ All projections are bias-free. Therefore the NITP condition has no
6077
+ additive route to the output and the transition still satisfies:
6078
 
6079
+ F(0, z) = 0
6080
+ dF(0, z) / dz = 0
6081
+
6082
+ The condition cannot construct a deep state by itself; it only controls
6083
+ features proposed by the current causal state. The module never
6084
+ materializes vocabulary logits.
6085
 
6086
  Parameter count, excluding the already-existing NITP projector:
6087
 
 
6097
  hidden_size = int(config.hidden_size)
6098
  intermediate_size = int(config.nitp_temporal_intermediate_size)
6099
 
6100
+ # The two inputs have different semantics and are normalized
6101
+ # independently: a final-layer causal state and a predicted shallow
6102
+ # next-state condition produced by the NITP projector.
6103
  self.context_norm = nn.RMSNorm(hidden_size, eps=config.rms_norm_eps)
6104
+ self.condition_norm = nn.RMSNorm(hidden_size, eps=config.rms_norm_eps)
6105
 
6106
+ # Context path: proposes the features of the deep-state update.
6107
  self.context_gate_proj = nn.Linear(
6108
  hidden_size, intermediate_size, bias=False
6109
  )
 
6111
  hidden_size, intermediate_size, bias=False
6112
  )
6113
 
6114
+ # NITP-condition path: controls contextual features but has no additive
6115
+ # route to the output. The centered tanh gate forces every non-zero
6116
+ # update to depend on the detached NITP condition.
6117
+ self.condition_modulation_proj = nn.Linear(
6118
  hidden_size, intermediate_size, bias=False
6119
  )
6120
  self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
 
6122
  def forward(
6123
  self,
6124
  current_states: torch.Tensor,
6125
+ next_shallow_conditions: torch.Tensor,
6126
  ) -> torch.Tensor:
6127
  context = self.context_norm(current_states)
6128
+ condition = self.condition_norm(next_shallow_conditions)
6129
 
6130
  context_update = (
6131
  F.silu(self.context_gate_proj(context))
6132
  * self.context_up_proj(context)
6133
  )
6134
+ signed_modulation = torch.tanh(
6135
+ self.condition_modulation_proj(condition)
6136
+ )
6137
+ delta = self.down_proj(context_update * signed_modulation)
6138
  return current_states + delta
6139
 
6140
 
 
6201
  """
6202
  Return a mask for contiguous temporal spans that stay inside one document.
6203
 
6204
+ A span includes the source state and every real future target used to
6205
+ evaluate the causal rollout. Padding / ignored labels invalidate the full
6206
+ span. EOS is permitted only at the final position, so no transition is
6207
+ trained across a packed-document boundary.
6208
  """
6209
  token_windows = valid_tokens.unfold(dimension=1, size=span_length, step=1)
6210
  span_mask = token_windows.all(dim=-1)
 
6216
  return span_mask
6217
 
6218
 
6219
+ def _project_nitp_with_frozen_weights(
6220
+ hidden_states: torch.Tensor,
6221
+ projector: NITPProjector,
6222
+ ) -> torch.Tensor:
6223
+ """
6224
+ Evaluate the current NITP projector while freezing only its parameters.
6225
+
6226
+ ``torch.no_grad()`` would also remove the derivative with respect to
6227
+ ``hidden_states``. The temporal identification objective instead needs:
6228
+
6229
+ d P_{sg(theta_P)}(h) / d h != 0
6230
+ d P_{sg(theta_P)}(h) / d theta_P = 0.
6231
+
6232
+ The explicit bias-free SwiGLU computation below therefore uses detached
6233
+ projector weights. Temporal gradients can move a rollout state into a
6234
+ region where the already-defined NITP representation remains valid, but
6235
+ they cannot turn the projector into a private code for the transition.
6236
+ """
6237
+ gate = F.linear(
6238
+ hidden_states,
6239
+ projector.gate_proj.weight.detach(),
6240
+ bias=None,
6241
+ )
6242
+ up = F.linear(
6243
+ hidden_states,
6244
+ projector.up_proj.weight.detach(),
6245
+ bias=None,
6246
+ )
6247
+ return F.linear(
6248
+ F.silu(gate) * up,
6249
+ projector.down_proj.weight.detach(),
6250
+ bias=None,
6251
+ )
6252
+
6253
+
6254
+ def _build_relational_temporal_similarity(
6255
  predicted_by_step: list[torch.Tensor],
6256
+ condition_by_step: list[torch.Tensor],
6257
  target_hidden_states: torch.Tensor,
6258
+ shallow_target_states: torch.Tensor,
6259
+ initial_hidden_states: torch.Tensor,
6260
  common_length: int,
6261
+ ) -> Tuple[
6262
+ torch.Tensor,
6263
+ torch.Tensor,
6264
+ torch.Tensor,
6265
+ torch.Tensor,
6266
+ ]:
6267
  """
6268
+ Build the local ``W x W`` temporal-identification matrices.
6269
+
6270
+ The trainable identification score keeps two conceptual halves:
6271
+
6272
+ S_{k,j} = 0.5 * [Gamma_{k,j} + Z_{k,j}],
6273
+
6274
+ where the geometric half is itself an equal-norm concatenation:
6275
+
6276
+ Gamma_{k,j} = 0.5 * [A_{k,j} + R_{k,j}].
6277
+
6278
+ The expanded score is therefore
6279
+
6280
+ S_{k,j} = 0.25 A_{k,j} + 0.25 R_{k,j} + 0.5 Z_{k,j}.
6281
+
6282
+ ``A`` is the direct accumulated-displacement cosine:
6283
+
6284
+ u_hat_k = normalize(h_hat_k - h_0)
6285
+ u_j^* = normalize(stopgrad(h_j^* - h_0))
6286
+ A_{k,j} = u_hat_k^T u_j^*.
6287
+
6288
+ ``R`` compares relational angular profiles instead of forcing the rollout
6289
+ to copy the teacher's local tangent. First compute
6290
+
6291
+ G^*_{j,r} = (u_j^*)^T u_r^*,
6292
+ rho_hat_k = normalize(A_{k,:}),
6293
+ rho_j^* = normalize(G^*_{j,:}),
6294
+ R_{k,j} = rho_hat_k^T rho_j^*.
6295
+
6296
+ If all rollout displacements collapse to one direction while the target
6297
+ trajectory has distinct directions, the rows ``A_{k,:}`` become nearly
6298
+ identical and cannot match the distinct target profiles ``G^*_{j,:}``.
6299
+ Conversely, if the target trajectory is genuinely parallel, its profiles
6300
+ are also similar, so this term does not invent artificial diversity.
6301
+
6302
+ ``Z`` is the frozen-weight NITP shallow-signature cosine. Projector
6303
+ parameters remain stop-gradient while gradients through the rollout state
6304
+ are preserved by ``_project_nitp_with_frozen_weights``.
6305
+
6306
+ No tunable mixing coefficient is introduced: 1/2 and 1/4 follow exactly
6307
+ from concatenating independently normalized blocks. The implementation
6308
+ deliberately evaluates the W-by-W cosines row-by-row instead of creating
6309
+ a ``[B,T,W,D]`` FP32 tensor, keeping peak memory small for W=4.
6310
  """
6311
+ horizon = len(predicted_by_step)
6312
+ initial_common = initial_hidden_states[:, :common_length, :]
6313
+ detached_initial = initial_common.detach()
6314
+
6315
+ displacement_rows = []
6316
  for predicted_states in predicted_by_step:
6317
+ predicted_displacement = (
6318
+ predicted_states[:, :common_length, :] - initial_common
6319
+ )
6320
  row = []
6321
+ for offset in range(1, horizon + 1):
6322
+ target_displacement = (
6323
+ target_hidden_states[
6324
+ :, offset : offset + common_length, :
6325
+ ].detach()
6326
+ - detached_initial
6327
+ )
6328
+ row.append(
6329
+ F.cosine_similarity(
6330
+ predicted_displacement.float(),
6331
+ target_displacement.float(),
6332
+ dim=-1,
6333
+ eps=1.0e-8,
6334
+ )
6335
+ )
6336
+ displacement_rows.append(torch.stack(row, dim=-1))
6337
+ displacement_similarity = torch.stack(displacement_rows, dim=-2)
6338
+
6339
+ target_gram_rows = []
6340
+ for left_offset in range(1, horizon + 1):
6341
+ left_displacement = (
6342
+ target_hidden_states[
6343
+ :, left_offset : left_offset + common_length, :
6344
+ ].detach()
6345
+ - detached_initial
6346
+ )
6347
+ row = []
6348
+ for right_offset in range(1, horizon + 1):
6349
+ right_displacement = (
6350
+ target_hidden_states[
6351
+ :, right_offset : right_offset + common_length, :
6352
+ ].detach()
6353
+ - detached_initial
6354
+ )
6355
+ row.append(
6356
+ F.cosine_similarity(
6357
+ left_displacement.float(),
6358
+ right_displacement.float(),
6359
+ dim=-1,
6360
+ eps=1.0e-8,
6361
+ )
6362
+ )
6363
+ target_gram_rows.append(torch.stack(row, dim=-1))
6364
+ target_gram = torch.stack(target_gram_rows, dim=-2)
6365
+
6366
+ predicted_profiles = F.normalize(
6367
+ displacement_similarity,
6368
+ p=2.0,
6369
+ dim=-1,
6370
+ eps=1.0e-8,
6371
+ )
6372
+ target_profiles = F.normalize(
6373
+ target_gram,
6374
+ p=2.0,
6375
+ dim=-1,
6376
+ eps=1.0e-8,
6377
+ )
6378
+ relational_similarity = torch.einsum(
6379
+ "btkr,btjr->btkj",
6380
+ predicted_profiles,
6381
+ target_profiles,
6382
+ )
6383
+
6384
+ nitp_rows = []
6385
+ for predicted_condition in condition_by_step:
6386
+ predicted_condition = predicted_condition[:, :common_length, :]
6387
+ row = []
6388
+ for offset in range(1, horizon + 1):
6389
+ target_condition = shallow_target_states[
6390
  :, offset : offset + common_length, :
6391
  ].detach()
6392
  row.append(
6393
  F.cosine_similarity(
6394
+ predicted_condition.float(),
6395
+ target_condition.float(),
6396
+ dim=-1,
6397
+ eps=1.0e-8,
6398
+ )
6399
+ )
6400
+ nitp_rows.append(torch.stack(row, dim=-1))
6401
+ nitp_signature_similarity = torch.stack(nitp_rows, dim=-2)
6402
+
6403
+ geometric_similarity = 0.5 * (
6404
+ displacement_similarity + relational_similarity
6405
+ )
6406
+ joint_similarity = 0.5 * (
6407
+ geometric_similarity + nitp_signature_similarity
6408
+ )
6409
+ return (
6410
+ joint_similarity,
6411
+ displacement_similarity,
6412
+ relational_similarity,
6413
+ nitp_signature_similarity,
6414
+ )
6415
+
6416
+
6417
+ def _temporal_matrix_statistics(
6418
+ matrix: torch.Tensor,
6419
+ window_mask: torch.Tensor,
6420
+ ) -> torch.Tensor:
6421
+ """
6422
+ Return ``[diagonal, off_diagonal, margin, positive_margin_fraction]``.
6423
+
6424
+ The margin is the diagonal score minus the hardest incorrect candidate in
6425
+ each row. ``positive_margin_fraction`` prevents a positive mean margin
6426
+ from hiding a large fraction of individually misidentified rows.
6427
+ """
6428
+ horizon = int(matrix.shape[-1])
6429
+ zero = matrix.new_zeros((), dtype=torch.float32)
6430
+ diagonal = matrix.diagonal(dim1=-2, dim2=-1)
6431
+ diagonal_mean = _masked_mean(diagonal.mean(dim=-1), window_mask)
6432
+ if horizon <= 1:
6433
+ return torch.stack((diagonal_mean, zero, zero, zero))
6434
+
6435
+ identity = torch.eye(horizon, dtype=torch.bool, device=matrix.device)
6436
+ identity = identity.view(1, 1, horizon, horizon)
6437
+ off_diagonal = matrix.masked_select(~identity).view(
6438
+ matrix.shape[0],
6439
+ matrix.shape[1],
6440
+ horizon * (horizon - 1),
6441
+ )
6442
+ off_diagonal_mean = _masked_mean(
6443
+ off_diagonal.mean(dim=-1),
6444
+ window_mask,
6445
+ )
6446
+ hard_negative = matrix.masked_fill(
6447
+ identity,
6448
+ torch.finfo(matrix.dtype).min,
6449
+ ).max(dim=-1).values
6450
+ row_margin = diagonal - hard_negative
6451
+ margin = _masked_mean(row_margin.mean(dim=-1), window_mask)
6452
+ positive_fraction = _masked_mean(
6453
+ (row_margin > 0.0).float().mean(dim=-1),
6454
+ window_mask,
6455
+ )
6456
+ return torch.stack(
6457
+ (diagonal_mean, off_diagonal_mean, margin, positive_fraction)
6458
+ )
6459
+
6460
+
6461
+ def _temporal_neighbor_cosines(
6462
+ matrix: torch.Tensor,
6463
+ window_mask: torch.Tensor,
6464
+ ) -> torch.Tensor:
6465
+ """Average matrix entries grouped by temporal distance ``|k-j|``."""
6466
+ horizon = int(matrix.shape[-1])
6467
+ if horizon <= 1:
6468
+ return matrix.new_zeros((0,), dtype=torch.float32)
6469
+
6470
+ indices = torch.arange(horizon, device=matrix.device)
6471
+ temporal_distance = (indices[:, None] - indices[None, :]).abs()
6472
+ values = []
6473
+ for offset in range(1, horizon):
6474
+ offset_mask = temporal_distance.eq(offset).view(
6475
+ 1, 1, horizon, horizon
6476
+ )
6477
+ selected = matrix.masked_select(offset_mask).view(
6478
+ matrix.shape[0],
6479
+ matrix.shape[1],
6480
+ -1,
6481
+ )
6482
+ values.append(_masked_mean(selected.mean(dim=-1), window_mask))
6483
+ return torch.stack(values)
6484
+
6485
+
6486
+ def _pairwise_cosine_matrix(states: torch.Tensor) -> torch.Tensor:
6487
+ """Return the within-trajectory cosine Gram matrix for ``[B,T,W,D]``."""
6488
+ unit = F.normalize(states.float(), p=2.0, dim=-1, eps=1.0e-8)
6489
+ return torch.einsum("btkd,btjd->btkj", unit, unit)
6490
+
6491
+
6492
+ def _pairwise_cosine_from_steps(
6493
+ states_by_step: list[torch.Tensor],
6494
+ origin: Optional[torch.Tensor] = None,
6495
+ ) -> torch.Tensor:
6496
+ """Low-peak-memory pair matrix from W tensors shaped ``[B,T,D]``."""
6497
+ rows = []
6498
+ for left in states_by_step:
6499
+ left_value = left if origin is None else left - origin
6500
+ row = []
6501
+ for right in states_by_step:
6502
+ right_value = right if origin is None else right - origin
6503
+ row.append(
6504
+ F.cosine_similarity(
6505
+ left_value.float(),
6506
+ right_value.float(),
6507
  dim=-1,
6508
+ eps=1.0e-8,
6509
  )
6510
  )
6511
+ rows.append(torch.stack(row, dim=-1))
6512
+ return torch.stack(rows, dim=-2)
6513
+
6514
+
6515
+ def _off_diagonal_pair_mean(
6516
+ matrix: torch.Tensor,
6517
+ window_mask: torch.Tensor,
6518
+ ) -> torch.Tensor:
6519
+ horizon = int(matrix.shape[-1])
6520
+ zero = matrix.new_zeros((), dtype=torch.float32)
6521
+ if horizon <= 1:
6522
+ return zero
6523
+ identity = torch.eye(horizon, dtype=torch.bool, device=matrix.device)
6524
+ selected = matrix.masked_select(
6525
+ ~identity.view(1, 1, horizon, horizon)
6526
+ ).view(
6527
+ matrix.shape[0],
6528
+ matrix.shape[1],
6529
+ horizon * (horizon - 1),
6530
+ )
6531
+ return _masked_mean(selected.mean(dim=-1), window_mask)
6532
 
6533
 
6534
  def compute_nitp_temporal_objective(
6535
  hidden_states: torch.Tensor,
6536
  shallow_target_states: torch.Tensor,
 
6537
  labels: torch.LongTensor,
6538
  attention_mask: Optional[torch.Tensor],
6539
  transition_model: NITPTemporalTransition,
 
6545
  target_temperature: float,
6546
  pad_token_id: Optional[int] = None,
6547
  eos_token_id: Optional[int] = None,
6548
+ ) -> Tuple[torch.Tensor, ...]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6549
  """
6550
+ Causal NITP temporal rollout with relational joint identification.
6551
 
6552
+ Autonomous rollout:
6553
 
6554
  h_hat_{t,0} = h_t
6555
+ z_hat_{t,k} = P_{stopgrad(theta_P)}(h_hat_{t,k-1})
6556
+ h_hat_{t,k} = T_psi(h_hat_{t,k-1}, stopgrad(z_hat_{t,k})).
6557
+
6558
+ The state objective remains the average Smooth-L1 alignment with real final
6559
+ states. The identification objective now uses
6560
+
6561
+ S = 0.25 A + 0.25 R + 0.5 Z,
6562
+
6563
+ where ``A`` is accumulated-displacement alignment, ``R`` is the relational
6564
+ angular-profile alignment across the full local future window, and ``Z``
6565
+ is frozen-weight NITP shallow-content alignment. This discourages an
6566
+ artificially parallel rollout only when the target trajectory itself has
6567
+ non-parallel angular structure. It does not add a direct diversity
6568
+ penalty, a new parameter, a new rollout, vocabulary logits, batch negatives
6569
+ or a tunable mixing coefficient.
6570
+
6571
+ Packed monitoring outputs are appended after the original fourteen scalar
6572
+ returns so old dashboards can remain intact:
6573
+
6574
+ ``component_metrics`` [4,4]
6575
+ rows: joint, displacement A, relational R, NITP signature Z
6576
+ cols: diagonal, off-diagonal, margin, positive-margin fraction.
6577
+
6578
+ ``step_metrics`` [11,W]
6579
+ rows: state loss, delta cosine, delta norm ratio, rollout-NITP cosine,
6580
+ trajectory specificity, cross-example predicted displacement cosine,
6581
+ cross-example target displacement cosine, their gap, predicted
6582
+ consecutive-delta cosine, target consecutive-delta cosine, their gap.
6583
+
6584
+ ``offset_metrics`` [4,W-1]
6585
+ rows use the same component order and columns group incorrect pairs by
6586
+ temporal distance 1..W-1.
6587
+
6588
+ ``scalar_metrics`` [6]
6589
+ path-length ratio, target absolute-state pair cosine, absolute pair gap,
6590
+ predicted centered-displacement pair cosine, target centered-
6591
+ displacement pair cosine, centered pair gap.
 
 
 
 
 
 
 
 
6592
  """
6593
  horizon = int(horizon)
6594
  seq_len = int(hidden_states.shape[1])
6595
  max_horizon = max(min(horizon, seq_len - 1), 0)
6596
  zero = hidden_states.new_zeros((), dtype=torch.float32)
6597
  if max_horizon == 0:
6598
+ return (
6599
+ *((zero,) * 14),
6600
+ zero.new_zeros((4, 4)),
6601
+ zero.new_zeros((11, 0)),
6602
+ zero.new_zeros((4, 0)),
6603
+ zero.new_zeros((6,)),
6604
+ )
6605
 
6606
  device = hidden_states.device
6607
  labels_on_device = labels.to(device=device)
 
6614
  valid_tokens = valid_tokens & (labels_on_device != pad_token_id)
6615
 
6616
  current_states = hidden_states
 
6617
  shifted_targets = hidden_states
6618
+ predicted_by_step: list[torch.Tensor] = []
6619
+ condition_by_step: list[torch.Tensor] = []
6620
+ step_masks: list[torch.Tensor] = []
6621
+ state_loss_by_step: list[torch.Tensor] = []
6622
+ delta_cosine_by_step: list[torch.Tensor] = []
6623
+ delta_norm_ratio_by_step: list[torch.Tensor] = []
6624
+ rollout_nitp_cosine_by_step: list[torch.Tensor] = []
6625
+ predicted_turn_by_step: list[torch.Tensor] = []
6626
+ target_turn_by_step: list[torch.Tensor] = []
6627
+
6628
  state_loss_total = zero
6629
+ path_predicted_norm_sum = zero
6630
+ path_target_norm_sum = zero
6631
+ previous_predicted_delta: Optional[torch.Tensor] = None
6632
+ previous_real_delta: Optional[torch.Tensor] = None
6633
 
6634
  for step in range(max_horizon):
6635
+ # The projector weights are detached but the derivative with respect to
6636
+ # the rollout state is preserved for the Z component. The transition
6637
+ # consumes a fully detached copy, preventing a private projector/
6638
+ # transition code.
6639
  source_states = current_states[:, :-1, :]
 
6640
  shifted_targets = shifted_targets[:, 1:, :]
6641
+ next_shallow_signature = _project_nitp_with_frozen_weights(
6642
+ source_states,
6643
+ nitp_projector,
6644
+ )
6645
+ predicted_states = transition_model(
6646
+ source_states,
6647
+ next_shallow_signature.detach(),
6648
+ )
6649
 
 
6650
  step_mask = _valid_temporal_span_mask(
6651
  valid_tokens=valid_tokens,
6652
  labels=labels_on_device,
6653
+ span_length=step + 2,
6654
  eos_token_id=eos_token_id,
6655
  )
6656
  step_masks.append(step_mask)
6657
+
6658
  step_state_loss = _masked_smooth_l1(
6659
  predicted=predicted_states,
6660
  target=shifted_targets,
6661
  mask=step_mask,
6662
  )
6663
  state_loss_total = state_loss_total + step_state_loss
6664
+ state_loss_by_step.append(step_state_loss.detach().float())
6665
 
 
 
 
 
6666
  with torch.no_grad():
6667
+ real_source_states = hidden_states[
6668
+ :, step : step + predicted_states.shape[1], :
6669
+ ]
6670
+ predicted_delta = predicted_states - source_states
6671
+ real_delta = shifted_targets - real_source_states
6672
+
6673
+ step_delta_cosine = F.cosine_similarity(
6674
+ predicted_delta.float(),
6675
+ real_delta.float(),
6676
+ dim=-1,
6677
+ eps=1.0e-8,
6678
+ )
6679
+ delta_cosine_by_step.append(
6680
+ _masked_mean(step_delta_cosine, step_mask)
6681
+ )
6682
+
6683
+ predicted_norm = predicted_delta.float().norm(dim=-1)
6684
+ real_norm = real_delta.float().norm(dim=-1)
6685
+ predicted_norm_mean = _masked_mean(predicted_norm, step_mask)
6686
+ real_norm_mean = _masked_mean(real_norm, step_mask)
6687
+ delta_norm_ratio_by_step.append(
6688
+ predicted_norm_mean / real_norm_mean.clamp_min(1.0e-8)
6689
+ )
6690
+ step_weights = step_mask.to(dtype=torch.float32)
6691
+ path_predicted_norm_sum = path_predicted_norm_sum + (
6692
+ predicted_norm * step_weights
6693
+ ).sum()
6694
+ path_target_norm_sum = path_target_norm_sum + (
6695
+ real_norm * step_weights
6696
+ ).sum()
6697
+
6698
  shallow_target = shallow_target_states[:, step + 1 :, :]
6699
+ step_nitp_cosine = F.cosine_similarity(
6700
+ next_shallow_signature.detach().float(),
 
6701
  shallow_target.float(),
6702
  dim=-1,
6703
+ eps=1.0e-8,
6704
  )
6705
+ rollout_nitp_cosine_by_step.append(
6706
+ _masked_mean(step_nitp_cosine, step_mask)
 
6707
  )
6708
 
6709
+ if previous_predicted_delta is None:
6710
+ predicted_turn_by_step.append(zero)
6711
+ target_turn_by_step.append(zero)
6712
+ else:
6713
+ previous_predicted_aligned = previous_predicted_delta[
6714
+ :, : predicted_delta.shape[1], :
6715
+ ]
6716
+ previous_real_aligned = previous_real_delta[
6717
+ :, : real_delta.shape[1], :
6718
+ ]
6719
+ predicted_turn = F.cosine_similarity(
6720
+ previous_predicted_aligned.float(),
6721
+ predicted_delta.float(),
6722
+ dim=-1,
6723
+ eps=1.0e-8,
6724
+ )
6725
+ target_turn = F.cosine_similarity(
6726
+ previous_real_aligned.float(),
6727
+ real_delta.float(),
6728
+ dim=-1,
6729
+ eps=1.0e-8,
6730
+ )
6731
+ predicted_turn_by_step.append(
6732
+ _masked_mean(predicted_turn, step_mask)
6733
+ )
6734
+ target_turn_by_step.append(
6735
+ _masked_mean(target_turn, step_mask)
6736
+ )
6737
+ previous_predicted_delta = predicted_delta.detach()
6738
+ previous_real_delta = real_delta.detach()
6739
+
6740
  predicted_by_step.append(predicted_states)
6741
+ condition_by_step.append(next_shallow_signature)
6742
  current_states = predicted_states
6743
 
6744
  inv_horizon = 1.0 / float(max_horizon)
6745
  state_loss = state_loss_total * inv_horizon
6746
+ state_step_tensor = torch.stack(state_loss_by_step)
6747
+ delta_step_tensor = torch.stack(delta_cosine_by_step)
6748
+ delta_norm_ratio_tensor = torch.stack(delta_norm_ratio_by_step)
6749
+ rollout_nitp_step_tensor = torch.stack(rollout_nitp_cosine_by_step)
6750
+ predicted_turn_tensor = torch.stack(predicted_turn_by_step)
6751
+ target_turn_tensor = torch.stack(target_turn_by_step)
6752
+ delta_cosine = delta_step_tensor.mean()
6753
+ rollout_nitp_cosine = rollout_nitp_step_tensor.mean()
6754
+ last_step_state_loss = state_step_tensor[-1]
6755
+ path_length_ratio = (
6756
+ path_predicted_norm_sum
6757
+ / path_target_norm_sum.clamp_min(1.0e-8)
6758
+ )
6759
 
 
 
6760
  common_length = seq_len - max_horizon
6761
  if common_length <= 0:
6762
+ zero_steps = zero.new_zeros((max_horizon,))
6763
+ step_metrics = torch.stack(
6764
+ (
6765
+ state_step_tensor,
6766
+ delta_step_tensor,
6767
+ delta_norm_ratio_tensor,
6768
+ rollout_nitp_step_tensor,
6769
+ zero_steps,
6770
+ zero_steps,
6771
+ zero_steps,
6772
+ zero_steps,
6773
+ predicted_turn_tensor,
6774
+ target_turn_tensor,
6775
+ predicted_turn_tensor - target_turn_tensor,
6776
+ )
6777
+ )
6778
  weighted_total = float(dynamics_weight) * state_loss
6779
  return (
6780
  weighted_total,
 
6786
  zero,
6787
  zero,
6788
  last_step_state_loss,
6789
+ delta_cosine,
6790
  rollout_nitp_cosine,
6791
  zero,
6792
  zero,
6793
  zero,
6794
+ zero.new_zeros((4, 4)),
6795
+ step_metrics,
6796
+ zero.new_zeros((4, max(max_horizon - 1, 0))),
6797
+ torch.stack((path_length_ratio, zero, zero, zero, zero, zero)),
6798
  )
6799
 
6800
+ (
6801
+ similarity,
6802
+ displacement_similarity,
6803
+ relational_similarity,
6804
+ nitp_signature_similarity,
6805
+ ) = _build_relational_temporal_similarity(
6806
  predicted_by_step=predicted_by_step,
6807
+ condition_by_step=condition_by_step,
6808
  target_hidden_states=hidden_states,
6809
+ shallow_target_states=shallow_target_states,
6810
+ initial_hidden_states=hidden_states,
6811
  common_length=common_length,
6812
+ )
6813
 
6814
  window_mask = _valid_temporal_span_mask(
6815
  valid_tokens=valid_tokens,
 
6837
 
6838
  row_prediction = similarity.argmax(dim=-1)
6839
  expected_offsets = offsets.view(1, 1, -1)
6840
+ temporal_top1_accuracy = _masked_mean(
6841
+ (row_prediction == expected_offsets).float().mean(dim=-1),
6842
+ window_mask,
6843
+ )
6844
  mean_absolute_offset = _masked_mean(
6845
  (row_prediction - expected_offsets).abs().float().mean(dim=-1),
6846
  window_mask,
6847
  )
6848
 
6849
+ # All decomposed diagnostics use detached matrices so they cannot retain
6850
+ # additional backward graphs. Their values still describe the exact
6851
+ # matrices used by the live identification objective.
6852
+ component_matrices = (
6853
+ similarity.detach(),
6854
+ displacement_similarity.detach(),
6855
+ relational_similarity.detach(),
6856
+ nitp_signature_similarity.detach(),
6857
+ )
6858
+ component_metrics = torch.stack(
6859
+ [
6860
+ _temporal_matrix_statistics(component, window_mask)
6861
+ for component in component_matrices
6862
+ ]
6863
+ )
6864
+ offset_metrics = torch.stack(
6865
+ [
6866
+ _temporal_neighbor_cosines(component, window_mask)
6867
+ for component in component_matrices
6868
+ ]
6869
+ )
6870
+ diagonal_cosine = component_metrics[0, 0]
6871
+ off_diagonal_cosine = component_metrics[0, 1]
6872
+ identification_margin = component_metrics[0, 2]
6873
 
6874
+ with torch.no_grad():
6875
+ initial_common = hidden_states[:, :common_length, :].detach()
6876
+ predicted_state_steps = [
6877
+ state[:, :common_length, :].detach()
6878
+ for state in predicted_by_step
6879
+ ]
6880
+ target_state_steps = [
6881
+ hidden_states[
6882
+ :, offset : offset + common_length, :
6883
+ ].detach()
6884
+ for offset in range(1, max_horizon + 1)
6885
+ ]
6886
+ rollout_pair_cosine = _off_diagonal_pair_mean(
6887
+ _pairwise_cosine_from_steps(predicted_state_steps),
6888
+ window_mask,
6889
  )
6890
+ target_pair_cosine = _off_diagonal_pair_mean(
6891
+ _pairwise_cosine_from_steps(target_state_steps),
6892
  window_mask,
6893
  )
6894
+ rollout_displacement_pair_cosine = _off_diagonal_pair_mean(
6895
+ _pairwise_cosine_from_steps(
6896
+ predicted_state_steps,
6897
+ origin=initial_common,
6898
+ ),
 
 
6899
  window_mask,
6900
  )
6901
+ target_displacement_pair_cosine = _off_diagonal_pair_mean(
6902
+ _pairwise_cosine_from_steps(
6903
+ target_state_steps,
6904
+ origin=initial_common,
6905
+ ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6906
  window_mask,
6907
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6908
 
6909
+ cross_predicted_by_step = []
6910
+ cross_target_by_step = []
6911
+ if int(hidden_states.shape[0]) > 1:
6912
  for step in range(max_horizon):
6913
+ step_mask_common = step_masks[step][:, :common_length]
6914
+ paired_mask = step_mask_common & torch.roll(
6915
+ step_mask_common,
6916
+ shifts=1,
6917
+ dims=0,
6918
  )
6919
+ predicted_displacement = (
6920
+ predicted_by_step[step][:, :common_length, :].detach()
6921
+ - initial_common
 
6922
  )
6923
+ target_displacement = (
6924
+ hidden_states[
6925
+ :, step + 1 : step + 1 + common_length, :
6926
+ ].detach()
6927
+ - initial_common
6928
+ )
6929
+ cross_predicted = F.cosine_similarity(
6930
+ predicted_displacement.float(),
6931
+ torch.roll(
6932
+ predicted_displacement.float(),
6933
+ shifts=1,
6934
+ dims=0,
6935
+ ),
6936
+ dim=-1,
6937
+ eps=1.0e-8,
6938
+ )
6939
+ cross_target = F.cosine_similarity(
6940
+ target_displacement.float(),
6941
+ torch.roll(
6942
+ target_displacement.float(),
6943
+ shifts=1,
6944
+ dims=0,
6945
+ ),
6946
+ dim=-1,
6947
+ eps=1.0e-8,
6948
  )
6949
+ cross_predicted_by_step.append(
6950
+ _masked_mean(cross_predicted, paired_mask)
6951
+ )
6952
+ cross_target_by_step.append(
6953
+ _masked_mean(cross_target, paired_mask)
6954
+ )
6955
+ else:
6956
+ cross_predicted_by_step = [zero] * max_horizon
6957
+ cross_target_by_step = [zero] * max_horizon
6958
+ cross_predicted_tensor = torch.stack(cross_predicted_by_step)
6959
+ cross_target_tensor = torch.stack(cross_target_by_step)
6960
 
6961
+ # Reuse the existing counterfactual rollout and retain each horizon's
6962
+ # specificity instead of discarding it into one final average.
6963
+ if int(hidden_states.shape[0]) > 1:
6964
+ wrong_current = torch.roll(hidden_states.detach(), shifts=1, dims=0)
6965
+ wrong_targets = hidden_states.detach()
6966
+ wrong_state_loss_by_step = []
6967
+ for step in range(max_horizon):
6968
+ wrong_source = wrong_current[:, :-1, :]
6969
+ wrong_targets = wrong_targets[:, 1:, :]
6970
+ wrong_condition = nitp_projector(wrong_source)
6971
+ wrong_predicted = transition_model(
6972
+ wrong_source,
6973
+ wrong_condition,
6974
+ )
6975
+ wrong_step_loss = _masked_smooth_l1(
6976
+ predicted=wrong_predicted,
6977
+ target=wrong_targets,
6978
+ mask=step_masks[step],
6979
+ )
6980
+ wrong_state_loss_by_step.append(wrong_step_loss.float())
6981
+ wrong_current = wrong_predicted
6982
+
6983
+ wrong_step_tensor = torch.stack(wrong_state_loss_by_step)
6984
+ correct_step_tensor = state_step_tensor.detach().float()
6985
+ trajectory_specificity_by_step = (
6986
+ (wrong_step_tensor - correct_step_tensor)
6987
+ / (wrong_step_tensor + correct_step_tensor + 1.0e-8)
6988
+ ).clamp(min=-1.0, max=1.0)
6989
+ wrong_state_loss = wrong_step_tensor.mean()
6990
+ full_state_loss = correct_step_tensor.mean()
6991
+ trajectory_specificity = (
6992
  (wrong_state_loss - full_state_loss)
6993
  / (wrong_state_loss + full_state_loss + 1.0e-8)
6994
  ).clamp(min=-1.0, max=1.0)
6995
  else:
6996
+ trajectory_specificity_by_step = zero.new_zeros((max_horizon,))
6997
+ trajectory_specificity = zero
6998
+
6999
+ step_metrics = torch.stack(
7000
+ (
7001
+ state_step_tensor,
7002
+ delta_step_tensor,
7003
+ delta_norm_ratio_tensor,
7004
+ rollout_nitp_step_tensor,
7005
+ trajectory_specificity_by_step,
7006
+ cross_predicted_tensor,
7007
+ cross_target_tensor,
7008
+ cross_predicted_tensor - cross_target_tensor,
7009
+ predicted_turn_tensor,
7010
+ target_turn_tensor,
7011
+ predicted_turn_tensor - target_turn_tensor,
7012
+ )
7013
+ )
7014
+ scalar_metrics = torch.stack(
7015
+ (
7016
+ path_length_ratio,
7017
+ target_pair_cosine,
7018
+ rollout_pair_cosine - target_pair_cosine,
7019
+ rollout_displacement_pair_cosine,
7020
+ target_displacement_pair_cosine,
7021
+ rollout_displacement_pair_cosine
7022
+ - target_displacement_pair_cosine,
7023
+ )
7024
+ )
7025
 
7026
  weighted_total = (
7027
  float(dynamics_weight) * state_loss
 
7037
  off_diagonal_cosine,
7038
  identification_margin,
7039
  last_step_state_loss,
7040
+ delta_cosine,
7041
  rollout_nitp_cosine,
7042
  rollout_pair_cosine,
7043
  valid_window_fraction,
7044
+ trajectory_specificity,
7045
+ component_metrics,
7046
+ step_metrics,
7047
+ offset_metrics,
7048
+ scalar_metrics,
7049
  )
7050
 
7051
 
 
7219
  self._last_nitp_temporal_rollout_nitp_cosine = None
7220
  self._last_nitp_temporal_rollout_pair_cosine = None
7221
  self._last_nitp_temporal_valid_window_fraction = None
7222
+ self._last_nitp_temporal_delta_cosine = None
7223
+ self._last_nitp_temporal_trajectory_specificity = None
7224
  self._last_nitp_temporal_to_nitp_ratio = None
7225
  self._last_nitp_temporal_loss_applied = None
7226
+ self._last_nitp_temporal_component_metrics = None
7227
+ self._last_nitp_temporal_step_metrics = None
7228
+ self._last_nitp_temporal_offset_metrics = None
7229
+ self._last_nitp_temporal_scalar_metrics = None
7230
  self._last_nextlat_loss = None
7231
  self._last_nextlat_mse_loss = None
7232
  self._last_nextlat_kl_loss = None
 
7376
  nitp_temporal_rollout_nitp_cosine = None
7377
  nitp_temporal_rollout_pair_cosine = None
7378
  nitp_temporal_valid_window_fraction = None
7379
+ nitp_temporal_delta_cosine = None
7380
+ nitp_temporal_trajectory_specificity = None
7381
+ nitp_temporal_component_metrics = None
7382
+ nitp_temporal_step_metrics = None
7383
+ nitp_temporal_offset_metrics = None
7384
+ nitp_temporal_scalar_metrics = None
7385
  nitp_temporal_to_nitp_ratio = None
7386
  nitp_target_hidden_states = None
7387
  nextlat_loss = None
 
7402
  self._last_nitp_temporal_rollout_nitp_cosine = None
7403
  self._last_nitp_temporal_rollout_pair_cosine = None
7404
  self._last_nitp_temporal_valid_window_fraction = None
7405
+ self._last_nitp_temporal_delta_cosine = None
7406
+ self._last_nitp_temporal_trajectory_specificity = None
7407
  self._last_nitp_temporal_to_nitp_ratio = None
7408
  self._last_nitp_temporal_loss_applied = None
7409
+ self._last_nitp_temporal_component_metrics = None
7410
+ self._last_nitp_temporal_step_metrics = None
7411
+ self._last_nitp_temporal_offset_metrics = None
7412
+ self._last_nitp_temporal_scalar_metrics = None
7413
  self._last_nextlat_loss = None
7414
  self._last_nextlat_mse_loss = None
7415
  self._last_nextlat_kl_loss = None
 
7491
  )
7492
  nitp_target_hidden_states = hidden_states_tuple[target_layer]
7493
 
 
7494
  temporal_apply_loss = bool(
7495
  getattr(self.config, "nitp_temporal_apply_loss", True)
7496
  )
 
7504
  temporal_results = compute_nitp_temporal_objective(
7505
  hidden_states=hidden_states,
7506
  shallow_target_states=nitp_target_hidden_states,
 
7507
  labels=labels,
7508
  attention_mask=attention_mask,
7509
  transition_model=self.nitp_temporal_transition,
 
7530
  temporal_results = compute_nitp_temporal_objective(
7531
  hidden_states=hidden_states,
7532
  shallow_target_states=nitp_target_hidden_states,
 
7533
  labels=labels,
7534
  attention_mask=attention_mask,
7535
  transition_model=self.nitp_temporal_transition,
 
7561
  nitp_temporal_off_diagonal_cosine,
7562
  nitp_temporal_identification_margin,
7563
  nitp_temporal_last_step_state_loss,
7564
+ nitp_temporal_delta_cosine,
7565
  nitp_temporal_rollout_nitp_cosine,
7566
  nitp_temporal_rollout_pair_cosine,
7567
  nitp_temporal_valid_window_fraction,
7568
+ nitp_temporal_trajectory_specificity,
7569
+ nitp_temporal_component_metrics,
7570
+ nitp_temporal_step_metrics,
7571
+ nitp_temporal_offset_metrics,
7572
+ nitp_temporal_scalar_metrics,
7573
  ) = temporal_results
7574
 
7575
  if temporal_apply_loss:
 
7588
  "NeoLLMForCausalLM.forward enables them automatically "
7589
  "while labels are present."
7590
  )
7591
+ # NextLat keeps its original teacher-forced token-embedding
7592
+ # path. This assignment is local to NextLat; causal Temporal
7593
+ # NITP no longer reads ``hidden_states_tuple[0]``.
7594
  token_embeds = hidden_states_tuple[0]
 
 
 
7595
  nextlat_loss, nextlat_mse_loss, nextlat_kl_loss = compute_nextlat_loss(
7596
  hidden_states=hidden_states,
7597
  token_embeds=token_embeds,
 
7678
  if nitp_temporal_valid_window_fraction is not None
7679
  else None
7680
  )
7681
+ self._last_nitp_temporal_delta_cosine = (
7682
+ nitp_temporal_delta_cosine.detach()
7683
+ if nitp_temporal_delta_cosine is not None
7684
+ else None
7685
+ )
7686
+ self._last_nitp_temporal_trajectory_specificity = (
7687
+ nitp_temporal_trajectory_specificity.detach()
7688
+ if nitp_temporal_trajectory_specificity is not None
7689
+ else None
7690
+ )
7691
+ self._last_nitp_temporal_component_metrics = (
7692
+ nitp_temporal_component_metrics.detach()
7693
+ if nitp_temporal_component_metrics is not None
7694
+ else None
7695
+ )
7696
+ self._last_nitp_temporal_step_metrics = (
7697
+ nitp_temporal_step_metrics.detach()
7698
+ if nitp_temporal_step_metrics is not None
7699
+ else None
7700
+ )
7701
+ self._last_nitp_temporal_offset_metrics = (
7702
+ nitp_temporal_offset_metrics.detach()
7703
+ if nitp_temporal_offset_metrics is not None
7704
+ else None
7705
+ )
7706
+ self._last_nitp_temporal_scalar_metrics = (
7707
+ nitp_temporal_scalar_metrics.detach()
7708
+ if nitp_temporal_scalar_metrics is not None
7709
  else None
7710
  )
7711
  self._last_nitp_temporal_to_nitp_ratio = (