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