KitsuVp commited on
Commit
50299f7
·
verified ·
1 Parent(s): aeb1e8a

Update modeling_neollm.py

Browse files
Files changed (1) hide show
  1. modeling_neollm.py +745 -239
modeling_neollm.py CHANGED
@@ -72,6 +72,7 @@ References:
72
  FP8 Training And Quantization For Dummies." arXiv:2511.23225.
73
  """
74
 
 
75
  import math
76
  from typing import Optional, Union, Tuple
77
 
@@ -87,6 +88,11 @@ except ImportError:
87
  linear_cross_entropy = None
88
  _CCE_AVAILABLE = False
89
 
 
 
 
 
 
90
  try:
91
  from liger_kernel.transformers import ( # type: ignore[import-not-found]
92
  LigerFusedLinearCrossEntropyLoss,
@@ -109,7 +115,7 @@ from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_u
109
  from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
110
  from transformers.processing_utils import Unpack
111
  from transformers.utils import TransformersKwargs, logging
112
- from .configuration_neollm import NeoLLMConfig
113
 
114
  from transformers import AutoConfig, AutoModel, AutoModelForCausalLM
115
 
@@ -5238,6 +5244,12 @@ class NeoLLMPreTrainedModel(PreTrainedModel):
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
@@ -5277,6 +5289,8 @@ class NeoLLMPreTrainedModel(PreTrainedModel):
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
 
@@ -5885,6 +5899,27 @@ def _resolve_liger_accum_dtype(accum_dtype):
5885
  )
5886
 
5887
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5888
  @torch.compiler.disable
5889
  def compute_cce_loss(
5890
  hidden_states,
@@ -5893,6 +5928,11 @@ def compute_cce_loss(
5893
  lm_head_bias=None,
5894
  pad_token_id=None,
5895
  cce_impl="cce_kahan_full_c",
 
 
 
 
 
5896
  ):
5897
  """CCE loss excluded from torch.compile, preserving the configured CCE impl."""
5898
  if linear_cross_entropy is None:
@@ -5905,6 +5945,22 @@ def compute_cce_loss(
5905
  processed_labels = _prepare_lm_labels(
5906
  labels, device=hidden_states.device, pad_token_id=pad_token_id
5907
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5908
  return linear_cross_entropy(
5909
  hidden_states,
5910
  lm_head_weight,
@@ -5913,6 +5969,7 @@ def compute_cce_loss(
5913
  shift=1,
5914
  impl=cce_impl,
5915
  reduction="mean",
 
5916
  )
5917
 
5918
 
@@ -6063,15 +6120,38 @@ class NITPTemporalTransition(nn.Module):
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:
@@ -6085,17 +6165,21 @@ class NITPTemporalTransition(nn.Module):
6085
 
6086
  Parameter count, excluding the already-existing NITP projector:
6087
 
6088
- 4 * d * m + 2d
6089
 
6090
- where ``m = nitp_temporal_intermediate_size`` and ``2d`` comes from the two
6091
- affine RMSNorm scales. With d=512 and m=1024 this is 2,098,176 trainable
6092
- parameters.
 
6093
  """
6094
 
6095
  def __init__(self, config: NeoLLMConfig):
6096
  super().__init__()
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
@@ -6112,17 +6196,33 @@ class NITPTemporalTransition(nn.Module):
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)
6121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)
@@ -6131,11 +6231,22 @@ class NITPTemporalTransition(nn.Module):
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
 
6141
  class NextLatDynamicsModel(nn.Module):
@@ -6531,6 +6642,170 @@ def _off_diagonal_pair_mean(
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,
@@ -6538,6 +6813,8 @@ def compute_nitp_temporal_objective(
6538
  attention_mask: Optional[torch.Tensor],
6539
  transition_model: NITPTemporalTransition,
6540
  nitp_projector: NITPProjector,
 
 
6541
  horizon: int,
6542
  dynamics_weight: float,
6543
  identification_weight: float,
@@ -6547,48 +6824,77 @@ def compute_nitp_temporal_objective(
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])
@@ -6596,11 +6902,11 @@ def compute_nitp_temporal_objective(
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
@@ -6613,29 +6919,71 @@ def compute_nitp_temporal_objective(
6613
  if pad_token_id is not None:
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(
@@ -6645,6 +6993,7 @@ def compute_nitp_temporal_objective(
6645
  predicted_states = transition_model(
6646
  source_states,
6647
  next_shallow_signature.detach(),
 
6648
  )
6649
 
6650
  step_mask = _valid_temporal_span_mask(
@@ -6663,13 +7012,125 @@ def compute_nitp_temporal_objective(
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(),
@@ -6743,15 +7204,32 @@ def compute_nitp_temporal_objective(
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)
@@ -6763,38 +7241,38 @@ def compute_nitp_temporal_objective(
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,
6781
  state_loss,
 
 
6782
  zero,
6783
  zero,
6784
  zero,
6785
  zero,
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
  (
@@ -6845,12 +7323,14 @@ def compute_nitp_temporal_objective(
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(),
@@ -6867,9 +7347,6 @@ def compute_nitp_temporal_objective(
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()
@@ -6958,97 +7435,56 @@ def compute_nitp_temporal_objective(
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
7028
  + float(identification_weight) * identification_loss
7029
  )
7030
  return (
7031
  weighted_total,
7032
  state_loss,
 
 
7033
  identification_loss,
7034
  temporal_top1_accuracy,
7035
  mean_absolute_offset,
7036
- diagonal_cosine,
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
-
7052
  def _categorical_kl_loss(
7053
  teacher_logits: torch.Tensor,
7054
  student_logits: torch.Tensor,
@@ -7204,25 +7640,44 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7204
  "`ntp_loss_backend` must be 'cce' or 'liger'. "
7205
  f"Got {self.ntp_loss_backend!r}."
7206
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7207
  self._last_ntp_loss = None
 
 
 
 
 
 
7208
  self._last_tweo_loss = None
7209
  self._last_nitp_loss = None
7210
  self._last_nitp_temporal_loss = None
7211
  self._last_nitp_temporal_state_loss = None
 
 
 
 
 
7212
  self._last_nitp_temporal_identification_loss = None
7213
  self._last_nitp_temporal_top1_accuracy = None
7214
  self._last_nitp_temporal_mean_absolute_offset = None
7215
- self._last_nitp_temporal_diagonal_cosine = None
7216
- self._last_nitp_temporal_off_diagonal_cosine = None
7217
  self._last_nitp_temporal_identification_margin = None
7218
- self._last_nitp_temporal_last_step_state_loss = None
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
@@ -7281,11 +7736,71 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7281
  position_ids: Optional[torch.LongTensor] = None,
7282
  inputs_embeds: Optional[torch.FloatTensor] = None,
7283
  labels: Optional[torch.LongTensor] = None,
 
7284
  logits_to_keep: Union[int, torch.Tensor] = 0,
7285
  output_hidden_states: Optional[bool] = None,
7286
  return_dict: Optional[bool] = None,
7287
  **kwargs: Unpack[TransformersKwargs],
7288
  ) -> CausalLMOutputWithPast:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7289
  tweo_enabled = (
7290
  bool(getattr(self.config, "use_tweo", False))
7291
  and labels is not None
@@ -7366,46 +7881,39 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7366
  nitp_loss = None
7367
  nitp_temporal_loss = None
7368
  nitp_temporal_state_loss = None
 
 
7369
  nitp_temporal_identification_loss = None
7370
  nitp_temporal_top1_accuracy = None
7371
  nitp_temporal_mean_absolute_offset = None
7372
- nitp_temporal_diagonal_cosine = None
7373
- nitp_temporal_off_diagonal_cosine = None
7374
  nitp_temporal_identification_margin = None
7375
- nitp_temporal_last_step_state_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
7388
  nextlat_mse_loss = None
7389
  nextlat_kl_loss = None
7390
  self._last_ntp_loss = None
 
 
 
7391
  self._last_tweo_loss = None
7392
  self._last_nitp_loss = None
7393
  self._last_nitp_temporal_loss = None
7394
  self._last_nitp_temporal_state_loss = None
 
 
 
 
 
7395
  self._last_nitp_temporal_identification_loss = None
7396
  self._last_nitp_temporal_top1_accuracy = None
7397
  self._last_nitp_temporal_mean_absolute_offset = None
7398
- self._last_nitp_temporal_diagonal_cosine = None
7399
- self._last_nitp_temporal_off_diagonal_cosine = None
7400
  self._last_nitp_temporal_identification_margin = None
7401
- self._last_nitp_temporal_last_step_state_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
@@ -7425,14 +7933,41 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7425
  self.liger_ntp_loss,
7426
  )
7427
  else:
7428
- ntp_loss = compute_cce_loss(
 
 
 
 
 
7429
  hidden_states,
7430
  labels,
7431
  self.lm_head.weight,
7432
  getattr(self.lm_head, "bias", None),
7433
  self.config.pad_token_id,
7434
  getattr(self.config, "cce_loss_impl", "cce_kahan_full_c"),
 
 
 
 
 
 
 
 
 
 
 
7435
  )
 
 
 
 
 
 
 
 
 
 
 
7436
  loss = ntp_loss
7437
 
7438
  if tweo_enabled:
@@ -7508,6 +8043,8 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7508
  attention_mask=attention_mask,
7509
  transition_model=self.nitp_temporal_transition,
7510
  nitp_projector=self.nitp_projector,
 
 
7511
  horizon=self.config.nitp_temporal_horizon,
7512
  dynamics_weight=self.config.nitp_temporal_dynamics_weight,
7513
  identification_weight=(
@@ -7534,6 +8071,8 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7534
  attention_mask=attention_mask,
7535
  transition_model=self.nitp_temporal_transition,
7536
  nitp_projector=self.nitp_projector,
 
 
7537
  horizon=self.config.nitp_temporal_horizon,
7538
  dynamics_weight=(
7539
  self.config.nitp_temporal_dynamics_weight
@@ -7554,18 +8093,13 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7554
  (
7555
  nitp_temporal_loss,
7556
  nitp_temporal_state_loss,
 
 
7557
  nitp_temporal_identification_loss,
7558
  nitp_temporal_top1_accuracy,
7559
  nitp_temporal_mean_absolute_offset,
7560
- nitp_temporal_diagonal_cosine,
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,
@@ -7575,12 +8109,6 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7575
  if temporal_apply_loss:
7576
  loss = loss + nitp_temporal_loss
7577
 
7578
- if nitp_loss is not None:
7579
- nitp_temporal_to_nitp_ratio = (
7580
- nitp_temporal_loss.detach().float()
7581
- / nitp_loss.detach().float().clamp_min(1e-8)
7582
- )
7583
-
7584
  if nextlat_enabled:
7585
  if hidden_states_tuple is None or len(hidden_states_tuple) < 2:
7586
  raise ValueError(
@@ -7628,6 +8156,16 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7628
  if nitp_temporal_state_loss is not None
7629
  else None
7630
  )
 
 
 
 
 
 
 
 
 
 
7631
  self._last_nitp_temporal_identification_loss = (
7632
  nitp_temporal_identification_loss.detach()
7633
  if nitp_temporal_identification_loss is not None
@@ -7643,51 +8181,16 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7643
  if nitp_temporal_mean_absolute_offset is not None
7644
  else None
7645
  )
7646
- self._last_nitp_temporal_diagonal_cosine = (
7647
- nitp_temporal_diagonal_cosine.detach()
7648
- if nitp_temporal_diagonal_cosine is not None
7649
- else None
7650
- )
7651
- self._last_nitp_temporal_off_diagonal_cosine = (
7652
- nitp_temporal_off_diagonal_cosine.detach()
7653
- if nitp_temporal_off_diagonal_cosine is not None
7654
- else None
7655
- )
7656
  self._last_nitp_temporal_identification_margin = (
7657
  nitp_temporal_identification_margin.detach()
7658
  if nitp_temporal_identification_margin is not None
7659
  else None
7660
  )
7661
- self._last_nitp_temporal_last_step_state_loss = (
7662
- nitp_temporal_last_step_state_loss.detach()
7663
- if nitp_temporal_last_step_state_loss is not None
7664
- else None
7665
- )
7666
- self._last_nitp_temporal_rollout_nitp_cosine = (
7667
- nitp_temporal_rollout_nitp_cosine.detach()
7668
- if nitp_temporal_rollout_nitp_cosine is not None
7669
- else None
7670
- )
7671
- self._last_nitp_temporal_rollout_pair_cosine = (
7672
- nitp_temporal_rollout_pair_cosine.detach()
7673
- if nitp_temporal_rollout_pair_cosine is not None
7674
- else None
7675
- )
7676
  self._last_nitp_temporal_valid_window_fraction = (
7677
  nitp_temporal_valid_window_fraction.detach()
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
@@ -7698,6 +8201,24 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
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
@@ -7708,21 +8229,6 @@ class NeoLLMForCausalLM(NeoLLMPreTrainedModel, GenerationMixin):
7708
  if nitp_temporal_scalar_metrics is not None
7709
  else None
7710
  )
7711
- self._last_nitp_temporal_to_nitp_ratio = (
7712
- nitp_temporal_to_nitp_ratio.detach()
7713
- if nitp_temporal_to_nitp_ratio is not None
7714
- else None
7715
- )
7716
- self._last_nitp_temporal_loss_applied = (
7717
- hidden_states.new_tensor(
7718
- 1.0
7719
- if bool(getattr(self.config, "nitp_temporal_apply_loss", True))
7720
- else 0.0,
7721
- dtype=torch.float32,
7722
- )
7723
- if nitp_temporal_enabled
7724
- else None
7725
- )
7726
  self._last_nextlat_loss = (
7727
  nextlat_loss.detach() if nextlat_loss is not None else None
7728
  )
 
72
  FP8 Training And Quantization For Dummies." arXiv:2511.23225.
73
  """
74
 
75
+ import inspect
76
  import math
77
  from typing import Optional, Union, Tuple
78
 
 
88
  linear_cross_entropy = None
89
  _CCE_AVAILABLE = False
90
 
91
+ try:
92
+ from cut_cross_entropy import meap_mask_inputs # type: ignore[import-not-found]
93
+ except ImportError:
94
+ meap_mask_inputs = None
95
+
96
  try:
97
  from liger_kernel.transformers import ( # type: ignore[import-not-found]
98
  LigerFusedLinearCrossEntropyLoss,
 
115
  from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
116
  from transformers.processing_utils import Unpack
117
  from transformers.utils import TransformersKwargs, logging
118
+ from configuration_neollm import NeoLLMConfig
119
 
120
  from transformers import AutoConfig, AutoModel, AutoModelForCausalLM
121
 
 
5244
  - down_proj: normal(0, 0.01) — keeps the residual transition close
5245
  to identity initially without blocking gradients into
5246
  the centered condition gate.
5247
+ - temporal_step_bias: zeros — all horizons start with the same phase.
5248
+ During the forward pass the bias table is projected to
5249
+ zero mean over the horizon, so it can represent only
5250
+ relative phase differences, not one shared phase offset.
5251
+ - temporal_step_gain_logits: zeros — 2*sigmoid(0)=1, so every horizon
5252
+ starts with exact unit residual gain.
5253
  - context_norm/condition_norm: weight=1.
5254
  NeoLLMAttention (Affine-Scaled Attention):
5255
  - alpha_proj: normal(0, 0.02) — near-zero so linear_clipping(≈0) ≈ 0.5
 
5289
  nn.init.xavier_uniform_(module.context_up_proj.weight)
5290
  nn.init.xavier_uniform_(module.condition_modulation_proj.weight)
5291
  nn.init.normal_(module.down_proj.weight, mean=0.0, std=0.01)
5292
+ nn.init.zeros_(module.temporal_step_bias)
5293
+ nn.init.zeros_(module.temporal_step_gain_logits)
5294
  module.context_norm.weight.data.fill_(1.0)
5295
  module.condition_norm.weight.data.fill_(1.0)
5296
 
 
5899
  )
5900
 
5901
 
5902
+ _EXTENDED_CCE_INSTALL = (
5903
+ "cut-cross-entropy @ "
5904
+ "git+https://github.com/Kitsunp/ml-cross-entropy.git@main"
5905
+ )
5906
+
5907
+
5908
+ def _require_extended_cce_options(*option_names: str) -> None:
5909
+ """Fail clearly only when an explicitly enabled CCE extension is unavailable."""
5910
+ if linear_cross_entropy is None:
5911
+ return
5912
+ parameters = inspect.signature(linear_cross_entropy).parameters
5913
+ missing = [name for name in option_names if name not in parameters]
5914
+ if missing:
5915
+ raise ImportError(
5916
+ "The installed cut-cross-entropy package does not provide the enabled "
5917
+ f"extension options {missing}. Install the extended backend with "
5918
+ f"`pip install --force-reinstall --no-deps \"{_EXTENDED_CCE_INSTALL}\"`, "
5919
+ "or disable the corresponding NeoLLM flags."
5920
+ )
5921
+
5922
+
5923
  @torch.compiler.disable
5924
  def compute_cce_loss(
5925
  hidden_states,
 
5928
  lm_head_bias=None,
5929
  pad_token_id=None,
5930
  cce_impl="cce_kahan_full_c",
5931
+ use_mile_loss=False,
5932
+ mile_loss_gamma=1.0,
5933
+ use_mu_loss=False,
5934
+ mu_loss_lambda=1e-4,
5935
+ return_loss_metrics=False,
5936
  ):
5937
  """CCE loss excluded from torch.compile, preserving the configured CCE impl."""
5938
  if linear_cross_entropy is None:
 
5945
  processed_labels = _prepare_lm_labels(
5946
  labels, device=hidden_states.device, pad_token_id=pad_token_id
5947
  )
5948
+ extension_kwargs = {}
5949
+ if use_mile_loss:
5950
+ _require_extended_cce_options("mile_enabled", "mile_gamma")
5951
+ extension_kwargs.update(
5952
+ mile_enabled=True,
5953
+ mile_gamma=float(mile_loss_gamma),
5954
+ )
5955
+ if use_mu_loss:
5956
+ _require_extended_cce_options("mu_loss_enabled", "mu_loss_lambda")
5957
+ extension_kwargs.update(
5958
+ mu_loss_enabled=True,
5959
+ mu_loss_lambda=float(mu_loss_lambda),
5960
+ )
5961
+ if return_loss_metrics:
5962
+ _require_extended_cce_options("return_loss_metrics")
5963
+ extension_kwargs["return_loss_metrics"] = True
5964
  return linear_cross_entropy(
5965
  hidden_states,
5966
  lm_head_weight,
 
5969
  shift=1,
5970
  impl=cce_impl,
5971
  reduction="mean",
5972
+ **extension_kwargs,
5973
  )
5974
 
5975
 
 
6120
  h_bar = RMSNorm_h(h_t)
6121
  z_bar = RMSNorm_z(stopgrad(P_NITP(h_t)))
6122
  u_h = SiLU(W_gate h_bar) * (W_up h_bar)
6123
+ b_tilde_k = b_k - mean_j(b_j)
6124
+ s_{z,k} = tanh(W_condition z_bar + b_tilde_k)
6125
+ g_k = 2 * sigmoid(a_k)
6126
+ delta_{t,k} = g_k * W_down(u_h * s_{z,k})
6127
+ h_hat_{t+1} = h_t + delta_{t,k}
6128
 
6129
+ The centered signed gate satisfies ``s_{z,k} in (-1, 1)``. Unlike a
6130
  positive sigmoid gate, it can attenuate, activate, or reverse an
6131
+ individual contextual feature. A zero-initialized learned vector ``b_k``
6132
+ modulates only the recurrent phase. Before indexing a horizon, the table is
6133
+ reparameterized as
6134
+
6135
+ b_tilde_k = b_k - mean_j(b_j),
6136
+
6137
+ so ``sum_k b_tilde_k = 0``. All horizons still share the same transition
6138
+ weights, but the bias table can encode only relative differences between
6139
+ rollout phases; a single offset shared by every horizon is removed exactly.
6140
+ A second zero-initialized scalar logit ``a_k`` supplies a positive bounded
6141
+ residual gain ``g_k = 2 sigmoid(a_k)`` for each horizon. Thus
6142
+ ``b_tilde_k`` controls the relative phase/channel pattern, while ``g_k``
6143
+ controls only its global amplitude. At initialization ``b_k=0`` and
6144
+ ``a_k=0`` for every horizon, hence ``b_tilde_k=0``, ``g_k=1``, and the model
6145
+ starts exactly from the previous transition. The step index is
6146
+ deterministic and contains no future-token information.
6147
+
6148
+ At initialization, ``W_condition=0`` implies ``s_{z,k}=0`` and therefore
6149
+ ``T_k(h, z)=h``. After training, a non-zero relative phase bias can provide
6150
+ a horizon-specific gate even when the condition projection is weak, but it
6151
+ cannot create the same bias offset at every horizon. The gain can rescale
6152
+ the resulting residual, yet neither phase nor gain creates an additive route
6153
+ through ``down_proj``: every output update remains multiplicatively tied to
6154
+ contextual features proposed by the current causal state.
6155
 
6156
  All projections are bias-free. Therefore the NITP condition has no
6157
  additive route to the output and the transition still satisfies:
 
6165
 
6166
  Parameter count, excluding the already-existing NITP projector:
6167
 
6168
+ 4 * d * m + 2d + W * m + W
6169
 
6170
+ where ``m = nitp_temporal_intermediate_size``, ``W`` is the temporal
6171
+ horizon, and ``2d`` comes from the two affine RMSNorm scales. With d=512,
6172
+ m=1024, and W=4 this is 2,102,276 trainable parameters: 4,100 more than
6173
+ the shared transition, of which only four are residual-gain parameters.
6174
  """
6175
 
6176
  def __init__(self, config: NeoLLMConfig):
6177
  super().__init__()
6178
  hidden_size = int(config.hidden_size)
6179
  intermediate_size = int(config.nitp_temporal_intermediate_size)
6180
+ horizon = int(config.nitp_temporal_horizon)
6181
+ if horizon < 1:
6182
+ raise ValueError("nitp_temporal_horizon must be at least 1")
6183
 
6184
  # The two inputs have different semantics and are normalized
6185
  # independently: a final-layer causal state and a predicted shallow
 
6196
  )
6197
 
6198
  # NITP-condition path: controls contextual features but has no additive
6199
+ # route to the output. The signed tanh gate is centered at zero; the
6200
+ # condition and the zero-mean relative phase bias modulate features
6201
+ # proposed by the current causal state.
6202
  self.condition_modulation_proj = nn.Linear(
6203
  hidden_size, intermediate_size, bias=False
6204
  )
6205
  self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
6206
 
6207
+ # Minimal phase conditioning. Each horizon receives one learned raw
6208
+ # bias in the shared intermediate gate. Forward reparameterizes this
6209
+ # table to zero mean over horizons, so only relative phase differences
6210
+ # are expressible through this branch. Zero initialization preserves
6211
+ # the exact previous transition at step 0 and avoids disturbing warmup.
6212
+ self.temporal_step_bias = nn.Parameter(
6213
+ torch.zeros(horizon, intermediate_size)
6214
+ )
6215
+
6216
+ # Minimal amplitude conditioning. One scalar logit per horizon is
6217
+ # mapped to a positive bounded gain in (0, 2). Zero initialization
6218
+ # gives exactly unit gain: 2 * sigmoid(0) = 1.
6219
+ self.temporal_step_gain_logits = nn.Parameter(torch.zeros(horizon))
6220
+
6221
  def forward(
6222
  self,
6223
  current_states: torch.Tensor,
6224
  next_shallow_conditions: torch.Tensor,
6225
+ step_index: int,
6226
  ) -> torch.Tensor:
6227
  context = self.context_norm(current_states)
6228
  condition = self.condition_norm(next_shallow_conditions)
 
6231
  F.silu(self.context_gate_proj(context))
6232
  * self.context_up_proj(context)
6233
  )
6234
+ # Remove the horizon-common bias mode without materializing a full
6235
+ # broadcast tensor. For horizon=1 this term is exactly zero, as there
6236
+ # is no relative phase to represent.
6237
+ relative_step_bias = (
6238
+ self.temporal_step_bias[step_index]
6239
+ - self.temporal_step_bias.mean(dim=0)
6240
+ )
6241
  signed_modulation = torch.tanh(
6242
  self.condition_modulation_proj(condition)
6243
+ + relative_step_bias
6244
  )
6245
  delta = self.down_proj(context_update * signed_modulation)
6246
+ step_gain = 2.0 * torch.sigmoid(
6247
+ self.temporal_step_gain_logits[step_index]
6248
+ )
6249
+ return current_states + step_gain * delta
6250
 
6251
 
6252
  class NextLatDynamicsModel(nn.Module):
 
6642
  return _masked_mean(selected.mean(dim=-1), window_mask)
6643
 
6644
 
6645
+ def _temporal_two_token_readout(
6646
+ student_states: torch.Tensor,
6647
+ teacher_states: torch.Tensor,
6648
+ candidate_labels: torch.LongTensor,
6649
+ candidate_valid: torch.Tensor,
6650
+ target_index: int,
6651
+ base_mask: torch.Tensor,
6652
+ lm_head_weight: torch.Tensor,
6653
+ lm_head_bias: Optional[torch.Tensor],
6654
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
6655
+ """
6656
+ Cheap functional anchor over the local temporal candidate set.
6657
+
6658
+ For the real state at horizon ``k`` the candidate set contains only the
6659
+ tokens attached to the W local future horizons. The strongest *different*
6660
+ temporal token under the real state is selected as the competitor. The
6661
+ rollout state then matches the teacher's binary distribution over
6662
+
6663
+ [token at the correct horizon, strongest incorrect temporal token].
6664
+
6665
+ This avoids a [B,T,W,V] or [B,T,V] vocabulary projection. It evaluates W
6666
+ teacher dot-products and two student dot-products, using detached LM-head
6667
+ rows so the auxiliary loss trains the temporal trajectory rather than the
6668
+ vocabulary head. No new temperature, loss weight, or trainable parameter
6669
+ is introduced.
6670
+ """
6671
+ horizon = int(candidate_labels.shape[-1])
6672
+ zero = student_states.new_zeros((), dtype=torch.float32)
6673
+ if horizon <= 1 or int(student_states.shape[1]) == 0:
6674
+ return zero, zero, zero, zero
6675
+
6676
+ detached_weight = lm_head_weight.detach()
6677
+ detached_bias = lm_head_bias.detach() if lm_head_bias is not None else None
6678
+ teacher = teacher_states.detach()
6679
+
6680
+ teacher_scores_by_candidate = []
6681
+ for candidate_index in range(horizon):
6682
+ token_ids = candidate_labels[..., candidate_index].clamp_min(0)
6683
+ token_rows = F.embedding(token_ids, detached_weight)
6684
+ score = (teacher * token_rows).sum(dim=-1, dtype=torch.float32)
6685
+ if detached_bias is not None:
6686
+ score = score + detached_bias[token_ids].float()
6687
+ teacher_scores_by_candidate.append(score)
6688
+ teacher_scores = torch.stack(teacher_scores_by_candidate, dim=-1)
6689
+
6690
+ target_ids = candidate_labels[..., target_index]
6691
+ safe_target_ids = target_ids.clamp_min(0)
6692
+ target_valid = candidate_valid[..., target_index]
6693
+ target_teacher_score = teacher_scores[..., target_index]
6694
+
6695
+ candidate_indices = torch.arange(
6696
+ horizon,
6697
+ device=candidate_labels.device,
6698
+ ).view(1, 1, horizon)
6699
+ competitor_mask = (
6700
+ candidate_valid
6701
+ & candidate_indices.ne(int(target_index))
6702
+ & candidate_labels.ne(target_ids.unsqueeze(-1))
6703
+ )
6704
+ has_competitor = competitor_mask.any(dim=-1)
6705
+ masked_teacher_scores = teacher_scores.masked_fill(
6706
+ ~competitor_mask,
6707
+ torch.finfo(teacher_scores.dtype).min,
6708
+ )
6709
+ competitor_index = masked_teacher_scores.argmax(dim=-1)
6710
+ competitor_ids = candidate_labels.gather(
6711
+ dim=-1,
6712
+ index=competitor_index.unsqueeze(-1),
6713
+ ).squeeze(-1)
6714
+ safe_competitor_ids = competitor_ids.clamp_min(0)
6715
+ competitor_teacher_score = teacher_scores.gather(
6716
+ dim=-1,
6717
+ index=competitor_index.unsqueeze(-1),
6718
+ ).squeeze(-1)
6719
+
6720
+ # Compute the two student logits sequentially so only one gathered
6721
+ # [B,T,H] LM-head block is live at a time.
6722
+ target_rows = F.embedding(safe_target_ids, detached_weight)
6723
+ target_student_score = (
6724
+ student_states * target_rows
6725
+ ).sum(dim=-1, dtype=torch.float32)
6726
+ competitor_rows = F.embedding(safe_competitor_ids, detached_weight)
6727
+ competitor_student_score = (
6728
+ student_states * competitor_rows
6729
+ ).sum(dim=-1, dtype=torch.float32)
6730
+ if detached_bias is not None:
6731
+ target_student_score = (
6732
+ target_student_score + detached_bias[safe_target_ids].float()
6733
+ )
6734
+ competitor_student_score = (
6735
+ competitor_student_score
6736
+ + detached_bias[safe_competitor_ids].float()
6737
+ )
6738
+
6739
+ teacher_pair_logits = torch.stack(
6740
+ (target_teacher_score, competitor_teacher_score),
6741
+ dim=-1,
6742
+ )
6743
+ student_pair_logits = torch.stack(
6744
+ (target_student_score, competitor_student_score),
6745
+ dim=-1,
6746
+ )
6747
+ readout_mask = base_mask & target_valid & has_competitor
6748
+
6749
+ teacher_log_probs = F.log_softmax(teacher_pair_logits.detach(), dim=-1)
6750
+ student_log_probs = F.log_softmax(student_pair_logits, dim=-1)
6751
+ per_token_kl = F.kl_div(
6752
+ student_log_probs,
6753
+ teacher_log_probs,
6754
+ log_target=True,
6755
+ reduction="none",
6756
+ ).sum(dim=-1)
6757
+ readout_loss = _masked_mean(per_token_kl, readout_mask)
6758
+
6759
+ with torch.no_grad():
6760
+ readout_agreement = _masked_mean(
6761
+ (
6762
+ teacher_pair_logits.argmax(dim=-1)
6763
+ == student_pair_logits.argmax(dim=-1)
6764
+ ).float(),
6765
+ readout_mask,
6766
+ )
6767
+ teacher_margin = (
6768
+ target_teacher_score - competitor_teacher_score
6769
+ )
6770
+ student_margin = (
6771
+ target_student_score - competitor_student_score
6772
+ )
6773
+ readout_margin_error = _masked_mean(
6774
+ (student_margin - teacher_margin).abs(),
6775
+ readout_mask,
6776
+ )
6777
+ readout_valid_fraction = readout_mask.float().mean()
6778
+
6779
+ return (
6780
+ readout_loss,
6781
+ readout_agreement,
6782
+ readout_margin_error,
6783
+ readout_valid_fraction,
6784
+ )
6785
+
6786
+
6787
+ NITP_TEMPORAL_STEP_METRIC_NAMES = (
6788
+ # Rows 0..12 are stable for backward-compatible trainer logging.
6789
+ "state_loss",
6790
+ "composite_step_loss",
6791
+ "delta_cosine",
6792
+ "delta_norm_ratio",
6793
+ "rollout_nitp_cosine",
6794
+ "readout_loss",
6795
+ "readout_agreement",
6796
+ "readout_margin_error",
6797
+ "readout_valid_fraction",
6798
+ "cross_example_pred_displacement_cosine",
6799
+ "cross_example_target_displacement_cosine",
6800
+ "predicted_delta_turn_cosine",
6801
+ "target_delta_turn_cosine",
6802
+ # Append-only component diagnostics for the new cheap moment objective.
6803
+ "raw_step_loss",
6804
+ "mean_error_loss",
6805
+ "std_match_loss",
6806
+ )
6807
+
6808
+
6809
  def compute_nitp_temporal_objective(
6810
  hidden_states: torch.Tensor,
6811
  shallow_target_states: torch.Tensor,
 
6813
  attention_mask: Optional[torch.Tensor],
6814
  transition_model: NITPTemporalTransition,
6815
  nitp_projector: NITPProjector,
6816
+ lm_head_weight: torch.Tensor,
6817
+ lm_head_bias: Optional[torch.Tensor],
6818
  horizon: int,
6819
  dynamics_weight: float,
6820
  identification_weight: float,
 
6824
  eos_token_id: Optional[int] = None,
6825
  ) -> Tuple[torch.Tensor, ...]:
6826
  """
6827
+ Causal Temporal-NITP with a cheap first-order trajectory objective.
6828
+
6829
+ The autonomous rollout and relational identification are unchanged:
6830
+
6831
+ h_hat_0 = h_0,
6832
+ z_hat_k = P_{sg(theta_P)}(h_hat_{k-1}),
6833
+ h_hat_k = T_k(h_hat_{k-1}, sg(z_hat_k)),
6834
+
6835
+ S = 0.25 A + 0.25 R + 0.5 Z.
6836
+
6837
+ Only the *dynamics objective* changes. The old objective supervised final
6838
+ states alone. The new objective is the fixed arithmetic mean
6839
+
6840
+ L_dyn = (L_position + L_step + L_readout) / 3.
6841
+
6842
+ ``L_position`` is the original Smooth-L1 state alignment. ``L_step`` uses
6843
+ target-RMS-normalized local increments and is now
6844
+
6845
+ L_step = L_raw + L_mean_error + 0.5 * L_std.
6846
+
6847
+ ``L_raw`` keeps the full per-window correspondence. ``L_mean_error`` applies
6848
+ Smooth-L1 only to the valid-window mean prediction error, directly penalizing
6849
+ a horizon-common direction that is absent from the targets. ``L_std`` matches
6850
+ the per-channel standard deviation around those means, retaining the useful
6851
+ lesson from the removed explicit centered loss: the rollout must preserve
6852
+ context-dependent dispersion instead of shrinking every example toward its
6853
+ batch mean. Means and second moments are reductions to [1,1,H]; no centered
6854
+ [B,S,H] tensors or second full-size Smooth-L1 branch are materialized.
6855
+ ``L_readout`` matches a two-token temporal readout selected from the existing
6856
+ W future labels; it protects functionally sensitive LM-head directions
6857
+ without projecting to the full vocabulary.
6858
+
6859
+ The constants 1/3 and 0.5 are not configurable hyperparameters. The first
6860
+ defines an equal mean of the three trajectory views; the second keeps the
6861
+ dispersion match auxiliary to the exact per-window and common-error terms.
6862
+ Existing ``dynamics_weight`` and ``identification_weight`` remain the only
6863
+ outer weights.
6864
+
6865
+ Packed diagnostics are intentionally compact and non-redundant:
6866
+
6867
+ ``component_metrics`` [3,4]
6868
+ rows: displacement A, relational R, NITP signature Z.
6869
+ cols: diagonal, off-diagonal, hardest-negative margin,
6870
+ positive-margin fraction. The joint diagonal/off-diagonal are omitted
6871
+ because they are exactly reconstructed as 0.25*A + 0.25*R + 0.5*Z;
6872
+ joint positive-margin fraction is identical to joint top-1.
6873
+
6874
+ ``step_metrics`` [16,W]
6875
+ Rows 0..12 preserve the previous order for logger compatibility: state
6876
+ loss, composite normalized step loss, delta cosine, delta norm ratio,
6877
+ rollout-NITP cosine, two-token readout KL, readout rank agreement,
6878
+ readout margin error, readout valid fraction, cross-example predicted
6879
+ displacement cosine, cross-example target displacement cosine,
6880
+ predicted consecutive-delta cosine, target consecutive-delta cosine.
6881
+ Rows 13..15 append the three step-loss components: raw local loss,
6882
+ common mean-error loss, and standard-deviation matching loss. The
6883
+ composite row is exactly raw + mean_error + 0.5*std. Aggregate component
6884
+ means are reconstructible by averaging the appended rows over W.
6885
+
6886
+ ``offset_metrics`` [3,W-1]
6887
+ neighbor similarities for A, R, and Z. The joint neighbor profile is
6888
+ omitted because it is exactly reconstructed from these three rows.
6889
+
6890
+ ``scalar_metrics`` [5]
6891
+ path-length ratio, predicted and target absolute-state pair cosine,
6892
+ predicted and target centered-displacement pair cosine. Pair gaps are
6893
+ omitted because they are simple differences.
6894
+
6895
+ The previous counterfactual trajectory-specificity rollout is removed. It
6896
+ required an additional W-step transition rollout and is replaced by the
6897
+ much cheaper per-step readout agreement plus cross-example diversity.
6898
  """
6899
  horizon = int(horizon)
6900
  seq_len = int(hidden_states.shape[1])
 
6902
  zero = hidden_states.new_zeros((), dtype=torch.float32)
6903
  if max_horizon == 0:
6904
  return (
6905
+ *((zero,) * 9),
6906
+ zero.new_zeros((3, 4)),
6907
+ zero.new_zeros((16, 0)),
6908
+ zero.new_zeros((3, 0)),
6909
+ zero.new_zeros((5,)),
6910
  )
6911
 
6912
  device = hidden_states.device
 
6919
  if pad_token_id is not None:
6920
  valid_tokens = valid_tokens & (labels_on_device != pad_token_id)
6921
 
6922
+ # The functional readout needs one token beyond the farthest hidden-state
6923
+ # target because h_{t+k} predicts label_{t+k+1}. Candidate labels are the W
6924
+ # true tokens attached to the local temporal horizons, so selecting the
6925
+ # strongest incorrect one costs O(W*H), not O(V*H).
6926
+ functional_length = max(seq_len - max_horizon - 1, 0)
6927
+ if functional_length > 0:
6928
+ candidate_labels = torch.stack(
6929
+ [
6930
+ labels_on_device[
6931
+ :, offset + 1 : offset + 1 + functional_length
6932
+ ]
6933
+ for offset in range(1, max_horizon + 1)
6934
+ ],
6935
+ dim=-1,
6936
+ )
6937
+ candidate_valid = torch.stack(
6938
+ [
6939
+ valid_tokens[
6940
+ :, offset + 1 : offset + 1 + functional_length
6941
+ ]
6942
+ for offset in range(1, max_horizon + 1)
6943
+ ],
6944
+ dim=-1,
6945
+ )
6946
+ functional_window_mask = _valid_temporal_span_mask(
6947
+ valid_tokens=valid_tokens,
6948
+ labels=labels_on_device,
6949
+ span_length=max_horizon + 2,
6950
+ eos_token_id=eos_token_id,
6951
+ )
6952
+ else:
6953
+ candidate_labels = None
6954
+ candidate_valid = None
6955
+ functional_window_mask = None
6956
+
6957
  current_states = hidden_states
6958
  shifted_targets = hidden_states
6959
  predicted_by_step: list[torch.Tensor] = []
6960
  condition_by_step: list[torch.Tensor] = []
6961
  step_masks: list[torch.Tensor] = []
6962
+
6963
  state_loss_by_step: list[torch.Tensor] = []
6964
+ normalized_step_loss_by_step: list[torch.Tensor] = []
6965
+ raw_step_loss_by_step: list[torch.Tensor] = []
6966
+ mean_error_loss_by_step: list[torch.Tensor] = []
6967
+ std_match_loss_by_step: list[torch.Tensor] = []
6968
  delta_cosine_by_step: list[torch.Tensor] = []
6969
  delta_norm_ratio_by_step: list[torch.Tensor] = []
6970
  rollout_nitp_cosine_by_step: list[torch.Tensor] = []
6971
+ readout_loss_by_step: list[torch.Tensor] = []
6972
+ readout_agreement_by_step: list[torch.Tensor] = []
6973
+ readout_margin_error_by_step: list[torch.Tensor] = []
6974
+ readout_valid_fraction_by_step: list[torch.Tensor] = []
6975
  predicted_turn_by_step: list[torch.Tensor] = []
6976
  target_turn_by_step: list[torch.Tensor] = []
6977
 
6978
  state_loss_total = zero
6979
+ normalized_step_loss_total = zero
6980
+ readout_loss_total = zero
6981
  path_predicted_norm_sum = zero
6982
  path_target_norm_sum = zero
6983
  previous_predicted_delta: Optional[torch.Tensor] = None
6984
  previous_real_delta: Optional[torch.Tensor] = None
6985
 
6986
  for step in range(max_horizon):
 
 
 
 
6987
  source_states = current_states[:, :-1, :]
6988
  shifted_targets = shifted_targets[:, 1:, :]
6989
  next_shallow_signature = _project_nitp_with_frozen_weights(
 
6993
  predicted_states = transition_model(
6994
  source_states,
6995
  next_shallow_signature.detach(),
6996
+ step_index=step,
6997
  )
6998
 
6999
  step_mask = _valid_temporal_span_mask(
 
7012
  state_loss_total = state_loss_total + step_state_loss
7013
  state_loss_by_step.append(step_state_loss.detach().float())
7014
 
7015
+ real_source_states = hidden_states[
7016
+ :, step : step + predicted_states.shape[1], :
7017
+ ].detach()
7018
+ predicted_delta = predicted_states - source_states
7019
+ real_delta = shifted_targets.detach() - real_source_states
7020
+
7021
+ # Scale each target delta to unit RMS. This is data-derived and detached,
7022
+ # so there is no new tuned scale and no radial shortcut.
7023
+ target_delta_rms = real_delta.float().pow(2).mean(
7024
+ dim=-1,
7025
+ keepdim=True,
7026
+ ).sqrt().clamp_min(1.0e-4)
7027
+ normalized_predicted_delta = predicted_delta.float() / target_delta_rms
7028
+ normalized_real_delta = real_delta.float() / target_delta_rms
7029
+
7030
+ # Absolute view: preserve the legitimate common temporal component as
7031
+ # well as each local direction and magnitude.
7032
+ raw_step_error = F.smooth_l1_loss(
7033
+ normalized_predicted_delta,
7034
+ normalized_real_delta,
7035
+ reduction="none",
7036
+ ).mean(dim=-1)
7037
+ raw_step_loss = _masked_mean(raw_step_error, step_mask)
7038
+
7039
+ # Cheap moment supervision over valid windows. Unlike the removed
7040
+ # explicit centered branch, this directly penalizes the common *error*
7041
+ # while preserving the full raw loss. The standard-deviation term keeps
7042
+ # the context-specific spread from collapsing. Only [1,1,H] reductions
7043
+ # survive; no centered [B,S,H] tensors are constructed.
7044
+ step_weights = step_mask.to(
7045
+ device=normalized_predicted_delta.device,
7046
+ dtype=normalized_predicted_delta.dtype,
7047
+ ).unsqueeze(-1)
7048
+ valid_step_count = step_weights.sum(
7049
+ dim=(0, 1),
7050
+ keepdim=True,
7051
+ ).clamp_min(1.0)
7052
+
7053
+ predicted_step_mean = (
7054
+ normalized_predicted_delta * step_weights
7055
+ ).sum(dim=(0, 1), keepdim=True) / valid_step_count
7056
+ target_step_mean = (
7057
+ normalized_real_delta * step_weights
7058
+ ).sum(dim=(0, 1), keepdim=True) / valid_step_count
7059
+
7060
+ mean_error_loss = F.smooth_l1_loss(
7061
+ predicted_step_mean,
7062
+ target_step_mean.detach(),
7063
+ reduction="mean",
7064
+ )
7065
+
7066
+ predicted_second_moment = (
7067
+ normalized_predicted_delta.square() * step_weights
7068
+ ).sum(dim=(0, 1), keepdim=True) / valid_step_count
7069
+ target_second_moment = (
7070
+ normalized_real_delta.square() * step_weights
7071
+ ).sum(dim=(0, 1), keepdim=True) / valid_step_count
7072
+
7073
+ # E[x^2] - E[x]^2 is clamped only for roundoff. The same epsilon on
7074
+ # both paths makes exactly constant prediction/target channels match.
7075
+ predicted_step_std = (
7076
+ predicted_second_moment - predicted_step_mean.square()
7077
+ ).clamp_min(0.0).add(1.0e-6).sqrt()
7078
+ target_step_std = (
7079
+ target_second_moment - target_step_mean.square()
7080
+ ).clamp_min(0.0).add(1.0e-6).sqrt()
7081
+ std_match_loss = F.smooth_l1_loss(
7082
+ predicted_step_std,
7083
+ target_step_std.detach(),
7084
+ reduction="mean",
7085
+ )
7086
+
7087
+ # Preserve the full local supervision, add direct pressure on the
7088
+ # horizon-common error, and retain a smaller dispersion constraint.
7089
+ step_delta_loss = (
7090
+ raw_step_loss + mean_error_loss + 0.5 * std_match_loss
7091
+ )
7092
+ normalized_step_loss_total = (
7093
+ normalized_step_loss_total + step_delta_loss
7094
+ )
7095
+ normalized_step_loss_by_step.append(step_delta_loss.detach().float())
7096
+ raw_step_loss_by_step.append(raw_step_loss.detach().float())
7097
+ mean_error_loss_by_step.append(mean_error_loss.detach().float())
7098
+ std_match_loss_by_step.append(std_match_loss.detach().float())
7099
+
7100
+ if functional_length > 0:
7101
+ (
7102
+ step_readout_loss,
7103
+ step_readout_agreement,
7104
+ step_readout_margin_error,
7105
+ step_readout_valid_fraction,
7106
+ ) = _temporal_two_token_readout(
7107
+ student_states=predicted_states[:, :functional_length, :],
7108
+ teacher_states=shifted_targets[:, :functional_length, :],
7109
+ candidate_labels=candidate_labels,
7110
+ candidate_valid=candidate_valid,
7111
+ target_index=step,
7112
+ base_mask=functional_window_mask,
7113
+ lm_head_weight=lm_head_weight,
7114
+ lm_head_bias=lm_head_bias,
7115
+ )
7116
+ else:
7117
+ step_readout_loss = zero
7118
+ step_readout_agreement = zero
7119
+ step_readout_margin_error = zero
7120
+ step_readout_valid_fraction = zero
7121
+ readout_loss_total = readout_loss_total + step_readout_loss
7122
+ readout_loss_by_step.append(step_readout_loss.detach().float())
7123
+ readout_agreement_by_step.append(
7124
+ step_readout_agreement.detach().float()
7125
+ )
7126
+ readout_margin_error_by_step.append(
7127
+ step_readout_margin_error.detach().float()
7128
+ )
7129
+ readout_valid_fraction_by_step.append(
7130
+ step_readout_valid_fraction.detach().float()
7131
+ )
7132
 
7133
+ with torch.no_grad():
7134
  step_delta_cosine = F.cosine_similarity(
7135
  predicted_delta.float(),
7136
  real_delta.float(),
 
7204
 
7205
  inv_horizon = 1.0 / float(max_horizon)
7206
  state_loss = state_loss_total * inv_horizon
7207
+ normalized_step_loss = normalized_step_loss_total * inv_horizon
7208
+ readout_loss = readout_loss_total * inv_horizon
7209
+ trajectory_loss = (
7210
+ state_loss + normalized_step_loss + readout_loss
7211
+ ) / 3.0
7212
+
7213
  state_step_tensor = torch.stack(state_loss_by_step)
7214
+ normalized_step_loss_tensor = torch.stack(
7215
+ normalized_step_loss_by_step
7216
+ )
7217
+ raw_step_loss_tensor = torch.stack(raw_step_loss_by_step)
7218
+ mean_error_loss_tensor = torch.stack(mean_error_loss_by_step)
7219
+ std_match_loss_tensor = torch.stack(std_match_loss_by_step)
7220
  delta_step_tensor = torch.stack(delta_cosine_by_step)
7221
  delta_norm_ratio_tensor = torch.stack(delta_norm_ratio_by_step)
7222
  rollout_nitp_step_tensor = torch.stack(rollout_nitp_cosine_by_step)
7223
+ readout_loss_tensor = torch.stack(readout_loss_by_step)
7224
+ readout_agreement_tensor = torch.stack(readout_agreement_by_step)
7225
+ readout_margin_error_tensor = torch.stack(
7226
+ readout_margin_error_by_step
7227
+ )
7228
+ readout_valid_fraction_tensor = torch.stack(
7229
+ readout_valid_fraction_by_step
7230
+ )
7231
  predicted_turn_tensor = torch.stack(predicted_turn_by_step)
7232
  target_turn_tensor = torch.stack(target_turn_by_step)
 
 
 
7233
  path_length_ratio = (
7234
  path_predicted_norm_sum
7235
  / path_target_norm_sum.clamp_min(1.0e-8)
 
7241
  step_metrics = torch.stack(
7242
  (
7243
  state_step_tensor,
7244
+ normalized_step_loss_tensor,
7245
  delta_step_tensor,
7246
  delta_norm_ratio_tensor,
7247
  rollout_nitp_step_tensor,
7248
+ readout_loss_tensor,
7249
+ readout_agreement_tensor,
7250
+ readout_margin_error_tensor,
7251
+ readout_valid_fraction_tensor,
7252
  zero_steps,
7253
  zero_steps,
7254
  predicted_turn_tensor,
7255
  target_turn_tensor,
7256
+ raw_step_loss_tensor,
7257
+ mean_error_loss_tensor,
7258
+ std_match_loss_tensor,
7259
  )
7260
  )
7261
+ weighted_total = float(dynamics_weight) * trajectory_loss
7262
  return (
7263
  weighted_total,
7264
  state_loss,
7265
+ normalized_step_loss,
7266
+ readout_loss,
7267
  zero,
7268
  zero,
7269
  zero,
7270
  zero,
7271
  zero,
7272
+ zero.new_zeros((3, 4)),
 
 
 
 
 
 
 
7273
  step_metrics,
7274
+ zero.new_zeros((3, max(max_horizon - 1, 0))),
7275
+ torch.stack((path_length_ratio, zero, zero, zero, zero)),
7276
  )
7277
 
7278
  (
 
7323
  (row_prediction - expected_offsets).abs().float().mean(dim=-1),
7324
  window_mask,
7325
  )
7326
+ identification_margin = _temporal_matrix_statistics(
7327
+ similarity.detach(),
7328
+ window_mask,
7329
+ )[2]
7330
 
7331
+ # A/R/Z component rows only. Joint diagonal/off-diagonal and neighbor
7332
+ # profiles are exact fixed-weight reconstructions and are not logged.
 
7333
  component_matrices = (
 
7334
  displacement_similarity.detach(),
7335
  relational_similarity.detach(),
7336
  nitp_signature_similarity.detach(),
 
7347
  for component in component_matrices
7348
  ]
7349
  )
 
 
 
7350
 
7351
  with torch.no_grad():
7352
  initial_common = hidden_states[:, :common_length, :].detach()
 
7435
  cross_predicted_tensor = torch.stack(cross_predicted_by_step)
7436
  cross_target_tensor = torch.stack(cross_target_by_step)
7437
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7438
  step_metrics = torch.stack(
7439
  (
7440
  state_step_tensor,
7441
+ normalized_step_loss_tensor,
7442
  delta_step_tensor,
7443
  delta_norm_ratio_tensor,
7444
  rollout_nitp_step_tensor,
7445
+ readout_loss_tensor,
7446
+ readout_agreement_tensor,
7447
+ readout_margin_error_tensor,
7448
+ readout_valid_fraction_tensor,
7449
  cross_predicted_tensor,
7450
  cross_target_tensor,
 
7451
  predicted_turn_tensor,
7452
  target_turn_tensor,
7453
+ raw_step_loss_tensor,
7454
+ mean_error_loss_tensor,
7455
+ std_match_loss_tensor,
7456
  )
7457
  )
7458
  scalar_metrics = torch.stack(
7459
  (
7460
  path_length_ratio,
7461
+ rollout_pair_cosine,
7462
  target_pair_cosine,
 
7463
  rollout_displacement_pair_cosine,
7464
  target_displacement_pair_cosine,
 
 
7465
  )
7466
  )
7467
 
7468
  weighted_total = (
7469
+ float(dynamics_weight) * trajectory_loss
7470
  + float(identification_weight) * identification_loss
7471
  )
7472
  return (
7473
  weighted_total,
7474
  state_loss,
7475
+ normalized_step_loss,
7476
+ readout_loss,
7477
  identification_loss,
7478
  temporal_top1_accuracy,
7479
  mean_absolute_offset,
 
 
7480
  identification_margin,
 
 
 
 
7481
  valid_window_fraction,
 
7482
  component_metrics,
7483
  step_metrics,
7484
  offset_metrics,
7485
  scalar_metrics,
7486
  )
7487
 
 
7488
  def _categorical_kl_loss(
7489
  teacher_logits: torch.Tensor,
7490
  student_logits: torch.Tensor,
 
7640
  "`ntp_loss_backend` must be 'cce' or 'liger'. "
7641
  f"Got {self.ntp_loss_backend!r}."
7642
  )
7643
+ if bool(getattr(config, "use_mile_loss", False)):
7644
+ _require_extended_cce_options("mile_enabled", "mile_gamma")
7645
+ if bool(getattr(config, "use_mu_loss", False)):
7646
+ _require_extended_cce_options("mu_loss_enabled", "mu_loss_lambda")
7647
+ if bool(getattr(config, "use_meap", False)) and meap_mask_inputs is None:
7648
+ raise ImportError(
7649
+ "`use_meap=True` requires the extended cut-cross-entropy package. "
7650
+ f"Install it with `pip install --force-reinstall --no-deps "
7651
+ f"\"{_EXTENDED_CCE_INSTALL}\"`, or set `use_meap=False`."
7652
+ )
7653
+ if bool(getattr(config, "use_meap", False)) and (
7654
+ "return_metrics" not in inspect.signature(meap_mask_inputs).parameters
7655
+ ):
7656
+ raise ImportError(
7657
+ "The installed cut-cross-entropy package does not provide kernel-side "
7658
+ "MEAP metrics. Reinstall the extended backend or set `use_meap=False`."
7659
+ )
7660
  self._last_ntp_loss = None
7661
+ self._last_ntp_ce_unweighted = None
7662
+ self._last_mile_reweighting_delta = None
7663
+ self._last_mu_loss = None
7664
+ self._last_meap_mask_fraction = None
7665
+ self._last_meap_masked_tokens = None
7666
+ self._last_meap_seed = None
7667
  self._last_tweo_loss = None
7668
  self._last_nitp_loss = None
7669
  self._last_nitp_temporal_loss = None
7670
  self._last_nitp_temporal_state_loss = None
7671
+ self._last_nitp_temporal_step_loss = None
7672
+ self._last_nitp_temporal_raw_step_loss = None
7673
+ self._last_nitp_temporal_mean_error_loss = None
7674
+ self._last_nitp_temporal_std_match_loss = None
7675
+ self._last_nitp_temporal_readout_loss = None
7676
  self._last_nitp_temporal_identification_loss = None
7677
  self._last_nitp_temporal_top1_accuracy = None
7678
  self._last_nitp_temporal_mean_absolute_offset = None
 
 
7679
  self._last_nitp_temporal_identification_margin = None
 
 
 
7680
  self._last_nitp_temporal_valid_window_fraction = None
 
 
 
 
7681
  self._last_nitp_temporal_component_metrics = None
7682
  self._last_nitp_temporal_step_metrics = None
7683
  self._last_nitp_temporal_offset_metrics = None
 
7736
  position_ids: Optional[torch.LongTensor] = None,
7737
  inputs_embeds: Optional[torch.FloatTensor] = None,
7738
  labels: Optional[torch.LongTensor] = None,
7739
+ meap_seed: Optional[int] = None,
7740
  logits_to_keep: Union[int, torch.Tensor] = 0,
7741
  output_hidden_states: Optional[bool] = None,
7742
  return_dict: Optional[bool] = None,
7743
  **kwargs: Unpack[TransformersKwargs],
7744
  ) -> CausalLMOutputWithPast:
7745
+ self._last_meap_mask_fraction = None
7746
+ self._last_meap_masked_tokens = None
7747
+ self._last_meap_seed = None
7748
+
7749
+ meap_enabled = (
7750
+ bool(getattr(self.config, "use_meap", False))
7751
+ and self.training
7752
+ and labels is not None
7753
+ )
7754
+ if meap_enabled:
7755
+ if input_ids is None or inputs_embeds is not None:
7756
+ raise ValueError(
7757
+ "MEAP requires `input_ids` and does not support precomputed "
7758
+ "`inputs_embeds`."
7759
+ )
7760
+ if meap_mask_inputs is None:
7761
+ raise ImportError(
7762
+ "MEAP is enabled but is unavailable in the installed "
7763
+ "cut-cross-entropy package."
7764
+ )
7765
+ mask_token_id = getattr(self.config, "meap_mask_token_id", None)
7766
+ if mask_token_id is None:
7767
+ raise ValueError("`meap_mask_token_id` is required when MEAP is enabled.")
7768
+ effective_meap_seed = int(
7769
+ getattr(self.config, "meap_seed", 0)
7770
+ if meap_seed is None
7771
+ else meap_seed
7772
+ )
7773
+ eligible_mask = (
7774
+ attention_mask.to(device=input_ids.device, dtype=torch.bool)
7775
+ if attention_mask is not None
7776
+ else None
7777
+ )
7778
+ exclude_last = bool(getattr(self.config, "meap_exclude_last", True))
7779
+ mask_ratio = float(getattr(self.config, "meap_mask_ratio", 0.15))
7780
+ input_ids, meap_metrics = meap_mask_inputs(
7781
+ input_ids,
7782
+ int(mask_token_id),
7783
+ enabled=True,
7784
+ mask_ratio=mask_ratio,
7785
+ eligible_mask=eligible_mask,
7786
+ seed=effective_meap_seed,
7787
+ exclude_last=exclude_last,
7788
+ return_mask=False,
7789
+ return_metrics=True,
7790
+ implementation=str(
7791
+ getattr(self.config, "meap_implementation", "triton")
7792
+ ),
7793
+ )
7794
+ eligible_count = meap_metrics[0]
7795
+ masked_count = meap_metrics[1]
7796
+ self._last_meap_mask_fraction = (
7797
+ masked_count.float() / eligible_count.clamp_min(1).float()
7798
+ ).detach()
7799
+ self._last_meap_masked_tokens = masked_count.detach()
7800
+ self._last_meap_seed = input_ids.new_tensor(
7801
+ effective_meap_seed, dtype=torch.long
7802
+ )
7803
+
7804
  tweo_enabled = (
7805
  bool(getattr(self.config, "use_tweo", False))
7806
  and labels is not None
 
7881
  nitp_loss = None
7882
  nitp_temporal_loss = None
7883
  nitp_temporal_state_loss = None
7884
+ nitp_temporal_step_loss = None
7885
+ nitp_temporal_readout_loss = None
7886
  nitp_temporal_identification_loss = None
7887
  nitp_temporal_top1_accuracy = None
7888
  nitp_temporal_mean_absolute_offset = None
 
 
7889
  nitp_temporal_identification_margin = None
 
 
 
7890
  nitp_temporal_valid_window_fraction = None
 
 
7891
  nitp_temporal_component_metrics = None
7892
  nitp_temporal_step_metrics = None
7893
  nitp_temporal_offset_metrics = None
7894
  nitp_temporal_scalar_metrics = None
 
7895
  nitp_target_hidden_states = None
7896
  nextlat_loss = None
7897
  nextlat_mse_loss = None
7898
  nextlat_kl_loss = None
7899
  self._last_ntp_loss = None
7900
+ self._last_ntp_ce_unweighted = None
7901
+ self._last_mile_reweighting_delta = None
7902
+ self._last_mu_loss = None
7903
  self._last_tweo_loss = None
7904
  self._last_nitp_loss = None
7905
  self._last_nitp_temporal_loss = None
7906
  self._last_nitp_temporal_state_loss = None
7907
+ self._last_nitp_temporal_step_loss = None
7908
+ self._last_nitp_temporal_raw_step_loss = None
7909
+ self._last_nitp_temporal_mean_error_loss = None
7910
+ self._last_nitp_temporal_std_match_loss = None
7911
+ self._last_nitp_temporal_readout_loss = None
7912
  self._last_nitp_temporal_identification_loss = None
7913
  self._last_nitp_temporal_top1_accuracy = None
7914
  self._last_nitp_temporal_mean_absolute_offset = None
 
 
7915
  self._last_nitp_temporal_identification_margin = None
 
 
 
7916
  self._last_nitp_temporal_valid_window_fraction = None
 
 
 
 
7917
  self._last_nitp_temporal_component_metrics = None
7918
  self._last_nitp_temporal_step_metrics = None
7919
  self._last_nitp_temporal_offset_metrics = None
 
7933
  self.liger_ntp_loss,
7934
  )
7935
  else:
7936
+ return_loss_metrics = bool(
7937
+ getattr(self.config, "use_mile_loss", False)
7938
+ or getattr(self.config, "use_mu_loss", False)
7939
+ or meap_enabled
7940
+ )
7941
+ ntp_result = compute_cce_loss(
7942
  hidden_states,
7943
  labels,
7944
  self.lm_head.weight,
7945
  getattr(self.lm_head, "bias", None),
7946
  self.config.pad_token_id,
7947
  getattr(self.config, "cce_loss_impl", "cce_kahan_full_c"),
7948
+ use_mile_loss=bool(
7949
+ getattr(self.config, "use_mile_loss", False)
7950
+ ),
7951
+ mile_loss_gamma=float(
7952
+ getattr(self.config, "mile_loss_gamma", 1.0)
7953
+ ),
7954
+ use_mu_loss=bool(getattr(self.config, "use_mu_loss", False)),
7955
+ mu_loss_lambda=float(
7956
+ getattr(self.config, "mu_loss_lambda", 1e-4)
7957
+ ),
7958
+ return_loss_metrics=return_loss_metrics,
7959
  )
7960
+ if return_loss_metrics:
7961
+ ntp_loss, ntp_metrics = ntp_result
7962
+ self._last_ntp_ce_unweighted = ntp_metrics[
7963
+ "ntp_ce_unweighted"
7964
+ ].detach()
7965
+ self._last_mile_reweighting_delta = ntp_metrics[
7966
+ "mile_reweighting_delta"
7967
+ ].detach()
7968
+ self._last_mu_loss = ntp_metrics["mu_loss"].detach()
7969
+ else:
7970
+ ntp_loss = ntp_result
7971
  loss = ntp_loss
7972
 
7973
  if tweo_enabled:
 
8043
  attention_mask=attention_mask,
8044
  transition_model=self.nitp_temporal_transition,
8045
  nitp_projector=self.nitp_projector,
8046
+ lm_head_weight=self.lm_head.weight,
8047
+ lm_head_bias=getattr(self.lm_head, "bias", None),
8048
  horizon=self.config.nitp_temporal_horizon,
8049
  dynamics_weight=self.config.nitp_temporal_dynamics_weight,
8050
  identification_weight=(
 
8071
  attention_mask=attention_mask,
8072
  transition_model=self.nitp_temporal_transition,
8073
  nitp_projector=self.nitp_projector,
8074
+ lm_head_weight=self.lm_head.weight,
8075
+ lm_head_bias=getattr(self.lm_head, "bias", None),
8076
  horizon=self.config.nitp_temporal_horizon,
8077
  dynamics_weight=(
8078
  self.config.nitp_temporal_dynamics_weight
 
8093
  (
8094
  nitp_temporal_loss,
8095
  nitp_temporal_state_loss,
8096
+ nitp_temporal_step_loss,
8097
+ nitp_temporal_readout_loss,
8098
  nitp_temporal_identification_loss,
8099
  nitp_temporal_top1_accuracy,
8100
  nitp_temporal_mean_absolute_offset,
 
 
8101
  nitp_temporal_identification_margin,
 
 
 
 
8102
  nitp_temporal_valid_window_fraction,
 
8103
  nitp_temporal_component_metrics,
8104
  nitp_temporal_step_metrics,
8105
  nitp_temporal_offset_metrics,
 
8109
  if temporal_apply_loss:
8110
  loss = loss + nitp_temporal_loss
8111
 
 
 
 
 
 
 
8112
  if nextlat_enabled:
8113
  if hidden_states_tuple is None or len(hidden_states_tuple) < 2:
8114
  raise ValueError(
 
8156
  if nitp_temporal_state_loss is not None
8157
  else None
8158
  )
8159
+ self._last_nitp_temporal_step_loss = (
8160
+ nitp_temporal_step_loss.detach()
8161
+ if nitp_temporal_step_loss is not None
8162
+ else None
8163
+ )
8164
+ self._last_nitp_temporal_readout_loss = (
8165
+ nitp_temporal_readout_loss.detach()
8166
+ if nitp_temporal_readout_loss is not None
8167
+ else None
8168
+ )
8169
  self._last_nitp_temporal_identification_loss = (
8170
  nitp_temporal_identification_loss.detach()
8171
  if nitp_temporal_identification_loss is not None
 
8181
  if nitp_temporal_mean_absolute_offset is not None
8182
  else None
8183
  )
 
 
 
 
 
 
 
 
 
 
8184
  self._last_nitp_temporal_identification_margin = (
8185
  nitp_temporal_identification_margin.detach()
8186
  if nitp_temporal_identification_margin is not None
8187
  else None
8188
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8189
  self._last_nitp_temporal_valid_window_fraction = (
8190
  nitp_temporal_valid_window_fraction.detach()
8191
  if nitp_temporal_valid_window_fraction is not None
8192
  else None
8193
  )
 
 
 
 
 
 
 
 
 
 
8194
  self._last_nitp_temporal_component_metrics = (
8195
  nitp_temporal_component_metrics.detach()
8196
  if nitp_temporal_component_metrics is not None
 
8201
  if nitp_temporal_step_metrics is not None
8202
  else None
8203
  )
8204
+ # Aggregate component metrics are derived from the append-only rows
8205
+ # 13..15. The existing `_last_nitp_temporal_step_loss` remains the
8206
+ # composite raw + mean_error + 0.5*std value for compatibility.
8207
+ if (
8208
+ nitp_temporal_step_metrics is not None
8209
+ and nitp_temporal_step_metrics.shape[0] >= 16
8210
+ and nitp_temporal_step_metrics.shape[1] > 0
8211
+ ):
8212
+ detached_step_metrics = nitp_temporal_step_metrics.detach()
8213
+ self._last_nitp_temporal_raw_step_loss = (
8214
+ detached_step_metrics[13].mean()
8215
+ )
8216
+ self._last_nitp_temporal_mean_error_loss = (
8217
+ detached_step_metrics[14].mean()
8218
+ )
8219
+ self._last_nitp_temporal_std_match_loss = (
8220
+ detached_step_metrics[15].mean()
8221
+ )
8222
  self._last_nitp_temporal_offset_metrics = (
8223
  nitp_temporal_offset_metrics.detach()
8224
  if nitp_temporal_offset_metrics is not None
 
8229
  if nitp_temporal_scalar_metrics is not None
8230
  else None
8231
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8232
  self._last_nextlat_loss = (
8233
  nextlat_loss.detach() if nextlat_loss is not None else None
8234
  )