KitsuVp commited on
Commit
bc28a32
·
verified ·
1 Parent(s): fb2f872

Update modeling_neollm.py

Browse files
Files changed (1) hide show
  1. modeling_neollm.py +280 -11
modeling_neollm.py CHANGED
@@ -8,8 +8,9 @@ generator, an optional Leviathan-JTok-M token-indexed modulation module,
8
  optional Spelling Bee Embeddings (Rabe et al., 2026), optional Context
9
  Re-Positioning (Li et al., 2026), optional REPO-GRAPE contextual group
10
  positioning (Li et al., 2026 + Zhang et al., 2026), optional GOAT-style
11
- factorised attention log-priors (Litman & Guo, 2026), and optional StackMemory
12
- (Zhang et al., NeurIPS 2025).
 
13
 
14
  Attention stack (orthogonal, all active simultaneously when enabled):
15
  1. Gated Attention (use_gated_attention implicit via q_proj gate chunk):
@@ -65,6 +66,8 @@ References:
65
  arXiv:2601.15380.
66
  StackMemory / STACKTRANS: Zhang et al. (2025). "Recursive Transformer:
67
  Boosting Reasoning Ability with State Stack." NeurIPS 2025.
 
 
68
  TWEO: Liang et al. (2026). "Transformers Without Extreme Outliers Enables
69
  FP8 Training And Quantization For Dummies." arXiv:2511.23225.
70
  """
@@ -2571,6 +2574,7 @@ class NeoLLMAttention(nn.Module):
2571
  Pipeline (all active simultaneously when enabled):
2572
  FANLayer → q_proj(gate) → q_norm/k_norm → REPO/RoPE or REPO-GRAPE-M
2573
  → Momentum → MEA(K,V) → LUCID(V) → v_ref → optional GOAT log-prior
 
2574
  → Affine-Scaled SDPA
2575
  → MEAHeadRMSNorm → XSA → Directional Routing → reshape
2576
  → o_proj · sigmoid(gate) → dropout
@@ -2727,6 +2731,51 @@ class NeoLLMAttention(nn.Module):
2727
  self.use_xsa = getattr(config, "use_xsa", False)
2728
  self.xsa_eps = float(getattr(config, "xsa_eps", 1e-6))
2729
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2730
  if self.use_affine_scaled_attention:
2731
  self.alpha_proj = nn.Linear(
2732
  config.hidden_size, config.num_attention_heads, bias=False
@@ -3248,6 +3297,207 @@ class NeoLLMAttention(nn.Module):
3248
  q_rot, k_rot = apply_rotary_pos_emb(q, k, cos, sin)
3249
  return q_rot, k_rot
3250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3251
  def _iha_seq_expand_flash_forward(
3252
  self,
3253
  q: torch.Tensor,
@@ -3260,6 +3510,7 @@ class NeoLLMAttention(nn.Module):
3260
  S_orig: int,
3261
  position_ids: Optional[torch.Tensor],
3262
  attention_mask: Optional[torch.Tensor] = None,
 
3263
  ) -> Tuple[torch.Tensor, None]:
3264
  """
3265
  Flash attention for the IHA seq-expand path (P>1).
@@ -3308,6 +3559,8 @@ class NeoLLMAttention(nn.Module):
3308
  dropout : attention dropout probability
3309
  S_orig : original (pre-expand) sequence length S
3310
  position_ids: [B, S] or None — for packing detection + cu_seqlens
 
 
3311
 
3312
  Returns
3313
  -------
@@ -3408,15 +3661,30 @@ class NeoLLMAttention(nn.Module):
3408
 
3409
  # ── Dense (non-packed) path ───────────────────────────────────────────
3410
  else:
3411
- flash_out = flash_attn_fn(
3412
- q_fa,
3413
- k_fa,
3414
- v_fa,
3415
- dropout_p=dropout if self.training else 0.0,
3416
- softmax_scale=self.scaling,
3417
- causal=True,
3418
- window_size=window,
3419
- ) # [B, S·P, H_q, d]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3420
 
3421
  # ── Affine-Scaled Attention inline (flash path) ───────────────────────
3422
  # Decomposition used here:
@@ -3860,6 +4128,7 @@ class NeoLLMAttention(nn.Module):
3860
  S_orig=_S_orig,
3861
  position_ids=position_ids,
3862
  attention_mask=attention_mask,
 
3863
  )
3864
  elif use_affine:
3865
  if self.config._attn_implementation == "eager":
 
8
  optional Spelling Bee Embeddings (Rabe et al., 2026), optional Context
9
  Re-Positioning (Li et al., 2026), optional REPO-GRAPE contextual group
10
  positioning (Li et al., 2026 + Zhang et al., 2026), optional GOAT-style
11
+ factorised attention log-priors (Litman & Guo, 2026), optional StackMemory
12
+ (Zhang et al., NeurIPS 2025), and optional HOLA-derived episodic memory for
13
+ the FlashAttention-2 IHA seq-expand path (Cui, 2026).
14
 
15
  Attention stack (orthogonal, all active simultaneously when enabled):
16
  1. Gated Attention (use_gated_attention implicit via q_proj gate chunk):
 
66
  arXiv:2601.15380.
67
  StackMemory / STACKTRANS: Zhang et al. (2025). "Recursive Transformer:
68
  Boosting Reasoning Ability with State Stack." NeurIPS 2025.
69
+ HOLA: Cui (2026). "A Hippocampus for Linear Attention: An Exact Memory
70
+ for What the Recurrent State Forgets." arXiv:2607.02303.
71
  TWEO: Liang et al. (2026). "Transformers Without Extreme Outliers Enables
72
  FP8 Training And Quantization For Dummies." arXiv:2511.23225.
73
  """
 
2574
  Pipeline (all active simultaneously when enabled):
2575
  FANLayer → q_proj(gate) → q_norm/k_norm → REPO/RoPE or REPO-GRAPE-M
2576
  → Momentum → MEA(K,V) → LUCID(V) → v_ref → optional GOAT log-prior
2577
+ → optional HOLA memory-augmented FA2 read (IHA seq-expand only)
2578
  → Affine-Scaled SDPA
2579
  → MEAHeadRMSNorm → XSA → Directional Routing → reshape
2580
  → o_proj · sigmoid(gate) → dropout
 
2731
  self.use_xsa = getattr(config, "use_xsa", False)
2732
  self.xsa_eps = float(getattr(config, "xsa_eps", 1e-6))
2733
 
2734
+ # ── HOLA-derived episodic memory (Cui, 2026) ─────────────────────────
2735
+ # This flag is deliberately scoped to the direct FlashAttention-2
2736
+ # seq-expand branch used by paper-correct IHA (P>1). It does not add a
2737
+ # second attention output. Instead, each chunk performs a single FA2 call
2738
+ # over [selected episodic KV, current chunk KV]. Memory logits live in an
2739
+ # augmented subspace so ordinary keys and memory keys can use different
2740
+ # read geometries while still sharing one softmax.
2741
+ #
2742
+ # Exact HOLA writes use beta*||e|| from a recurrent delta state. This
2743
+ # Transformer branch has no such state and stock FA2 does not expose the
2744
+ # pre-dropout accumulators needed for exact leave-one-out residuals.
2745
+ # Therefore the FA2 path below uses a compile-friendly value-innovation
2746
+ # proxy for episode eviction. The read path itself is exact within the
2747
+ # single augmented FA2 softmax.
2748
+ self.use_hola_memory = bool(getattr(config, "use_hola_memory", False))
2749
+ self.hola_memory_size = int(getattr(config, "hola_memory_size", 64))
2750
+ self.hola_chunk_size = int(getattr(config, "hola_chunk_size", 256))
2751
+ self.hola_score_eps = float(getattr(config, "hola_score_eps", 1e-6))
2752
+
2753
+ if self.use_hola_memory:
2754
+ self.hola_q_gamma = nn.Parameter(
2755
+ torch.full(
2756
+ (config.num_attention_heads, self.head_dim),
2757
+ float(getattr(config, "hola_rms_gamma_init", 1.0)),
2758
+ )
2759
+ )
2760
+ self.hola_k_gamma = nn.Parameter(
2761
+ torch.full(
2762
+ (config.num_key_value_heads, self.head_dim),
2763
+ float(getattr(config, "hola_rms_gamma_init", 1.0)),
2764
+ )
2765
+ )
2766
+ # Tied at KV-head granularity because memory keys are stored once per
2767
+ # KV component. Query heads that share a KV component share this prior.
2768
+ self.hola_logit_bias = nn.Parameter(
2769
+ torch.full(
2770
+ (config.num_key_value_heads,),
2771
+ float(getattr(config, "hola_logit_bias_init", -2.0)),
2772
+ )
2773
+ )
2774
+ else:
2775
+ self.hola_q_gamma = None
2776
+ self.hola_k_gamma = None
2777
+ self.hola_logit_bias = None
2778
+
2779
  if self.use_affine_scaled_attention:
2780
  self.alpha_proj = nn.Linear(
2781
  config.hidden_size, config.num_attention_heads, bias=False
 
3297
  q_rot, k_rot = apply_rotary_pos_emb(q, k, cos, sin)
3298
  return q_rot, k_rot
3299
 
3300
+
3301
+ def _hola_rms_gamma(
3302
+ self,
3303
+ x: torch.Tensor,
3304
+ gamma: torch.Tensor,
3305
+ ) -> torch.Tensor:
3306
+ """
3307
+ HOLA cache-read normalization used only for the memory subspace.
3308
+
3309
+ Args:
3310
+ x: [B, T, H, d] tensor in flash-attn layout.
3311
+ gamma: [H, d] learned RMSNorm-γ scale.
3312
+ """
3313
+ rms = x.float().pow(2).mean(dim=-1, keepdim=True).add(self.hola_score_eps).rsqrt()
3314
+ return (x.float() * rms * gamma.view(1, 1, gamma.shape[0], gamma.shape[1]).float()).to(x.dtype)
3315
+
3316
+ def _hola_value_innovation_scores(
3317
+ self,
3318
+ v: torch.Tensor,
3319
+ gate_states: Optional[torch.Tensor],
3320
+ S_orig: int,
3321
+ ) -> torch.Tensor:
3322
+ """
3323
+ FA2-compatible HOLA-derived eviction proxy.
3324
+
3325
+ Full HOLA writes by beta*||e|| where e is the committed residual of a
3326
+ recurrent delta state. This Transformer/FA2 path intentionally avoids a
3327
+ second attention pass and stock FA2 does not return the pre-dropout
3328
+ leave-one-out accumulators needed for the exact score. We therefore use
3329
+ a causal value-innovation proxy:
3330
+
3331
+ score_t = || V_bundle(t) - V_bundle(t-1) ||_rms * gate_strength_t
3332
+
3333
+ The score is computed per *real* token and aggregates all pseudo-slots,
3334
+ so TopK stores complete IHA episodes rather than isolated virtual slots.
3335
+ """
3336
+ B, H_kv, SP, d_head = v.shape
3337
+ P = self.iha_P
3338
+ v_tok = v.reshape(B, H_kv, S_orig, P, d_head).float()
3339
+ prev = torch.cat([torch.zeros_like(v_tok[:, :, :1]), v_tok[:, :, :-1]], dim=2)
3340
+ delta = v_tok - prev
3341
+ scores = delta.pow(2).mean(dim=(1, 3, 4)).add(self.hola_score_eps).sqrt() # [B, S]
3342
+
3343
+ if gate_states is not None:
3344
+ gate = torch.sigmoid(gate_states).view(B, S_orig, self.config.num_attention_heads, d_head)
3345
+ gate_strength = gate.float().pow(2).mean(dim=(2, 3)).add(self.hola_score_eps).sqrt()
3346
+ scores = scores * gate_strength
3347
+ return scores
3348
+
3349
+ def _hola_augmented_fa_call(
3350
+ self,
3351
+ q_cur: torch.Tensor,
3352
+ k_cur: torch.Tensor,
3353
+ v_cur: torch.Tensor,
3354
+ k_mem: Optional[torch.Tensor],
3355
+ v_mem: Optional[torch.Tensor],
3356
+ dropout: float,
3357
+ window_left: int,
3358
+ ) -> torch.Tensor:
3359
+ """
3360
+ Single-FA2 HOLA read using augmented Q/K/V subspaces.
3361
+
3362
+ Ordinary keys use the first d channels:
3363
+ <sqrt(D/d) q, k> / sqrt(D) = <q,k> / sqrt(d)
3364
+
3365
+ Memory keys use the second d channels plus one constant-bias channel:
3366
+ <sqrt(D/d) q_H, k_H> / sqrt(D) + b
3367
+
3368
+ Values are padded with zeros and sliced back to d after FA2. The result
3369
+ is one softmax over [memory, current chunk], not a separate cache
3370
+ attention output.
3371
+ """
3372
+ flash_attn_fn = self._iha_flash_attn_func
3373
+ B, Tq, H_q, d_head = q_cur.shape
3374
+ H_kv = k_cur.shape[2]
3375
+ base_aug = 2 * d_head + 1
3376
+ d_aug = ((base_aug + 7) // 8) * 8
3377
+ pad_tail = d_aug - base_aug
3378
+ scale = math.sqrt(float(d_aug) / float(d_head))
3379
+ softmax_scale = 1.0 / math.sqrt(float(d_aug))
3380
+
3381
+ q_h = self._hola_rms_gamma(q_cur, self.hola_q_gamma)
3382
+ one_q = torch.ones(B, Tq, H_q, 1, device=q_cur.device, dtype=q_cur.dtype)
3383
+ q_parts = [q_cur * scale, q_h * scale, one_q]
3384
+ if pad_tail > 0:
3385
+ q_parts.append(torch.zeros(B, Tq, H_q, pad_tail, device=q_cur.device, dtype=q_cur.dtype))
3386
+ q_aug = torch.cat(q_parts, dim=-1).contiguous()
3387
+
3388
+ zeros_cur = torch.zeros_like(k_cur)
3389
+ zero_bias_cur = torch.zeros(B, k_cur.shape[1], H_kv, 1, device=k_cur.device, dtype=k_cur.dtype)
3390
+ k_cur_parts = [k_cur, zeros_cur, zero_bias_cur]
3391
+ if pad_tail > 0:
3392
+ k_cur_parts.append(torch.zeros(B, k_cur.shape[1], H_kv, pad_tail, device=k_cur.device, dtype=k_cur.dtype))
3393
+ k_cur_aug = torch.cat(k_cur_parts, dim=-1)
3394
+
3395
+ v_cur_pad = torch.zeros(B, v_cur.shape[1], H_kv, d_aug - d_head, device=v_cur.device, dtype=v_cur.dtype)
3396
+ v_cur_aug = torch.cat([v_cur, v_cur_pad], dim=-1)
3397
+
3398
+ if k_mem is not None and k_mem.shape[1] > 0:
3399
+ k_mem_h = self._hola_rms_gamma(k_mem, self.hola_k_gamma)
3400
+ zeros_mem = torch.zeros_like(k_mem)
3401
+ bias = (self.hola_logit_bias.view(1, 1, H_kv, 1).to(k_mem.dtype) * math.sqrt(float(d_aug)))
3402
+ bias = bias.expand(B, k_mem.shape[1], H_kv, 1)
3403
+ k_mem_parts = [zeros_mem, k_mem_h, bias]
3404
+ if pad_tail > 0:
3405
+ k_mem_parts.append(torch.zeros(B, k_mem.shape[1], H_kv, pad_tail, device=k_mem.device, dtype=k_mem.dtype))
3406
+ k_aug = torch.cat([torch.cat(k_mem_parts, dim=-1), k_cur_aug], dim=1).contiguous()
3407
+
3408
+ v_mem_pad = torch.zeros(B, v_mem.shape[1], H_kv, d_aug - d_head, device=v_mem.device, dtype=v_mem.dtype)
3409
+ v_mem_aug = torch.cat([v_mem, v_mem_pad], dim=-1)
3410
+ v_aug = torch.cat([v_mem_aug, v_cur_aug], dim=1).contiguous()
3411
+ mem_len = k_mem.shape[1]
3412
+ else:
3413
+ k_aug = k_cur_aug.contiguous()
3414
+ v_aug = v_cur_aug.contiguous()
3415
+ mem_len = 0
3416
+
3417
+ # When local IHA is active, memory tokens are placed immediately before
3418
+ # the current chunk. Enlarging the left window by mem_len makes all
3419
+ # selected episodes visible while preserving causal masking inside the
3420
+ # current chunk. For global layers window_left is -1.
3421
+ if window_left >= 0:
3422
+ window = (mem_len + window_left, 0)
3423
+ else:
3424
+ window = (-1, -1)
3425
+
3426
+ out_aug = flash_attn_fn(
3427
+ q_aug,
3428
+ k_aug,
3429
+ v_aug,
3430
+ dropout_p=dropout if self.training else 0.0,
3431
+ softmax_scale=softmax_scale,
3432
+ causal=True,
3433
+ window_size=window,
3434
+ )
3435
+ return out_aug[..., :d_head].contiguous()
3436
+
3437
+ def _iha_hola_seq_expand_flash_forward(
3438
+ self,
3439
+ q: torch.Tensor,
3440
+ k: torch.Tensor,
3441
+ v: torch.Tensor,
3442
+ dropout: float,
3443
+ S_orig: int,
3444
+ gate_states: Optional[torch.Tensor] = None,
3445
+ ) -> torch.Tensor:
3446
+ """
3447
+ HOLA-derived episodic memory for the dense IHA seq-expand FA2 path.
3448
+
3449
+ The sequence is processed in original-token chunks. At the beginning of
3450
+ a chunk, a fixed-size TopK memory is selected from strictly previous
3451
+ tokens using the value-innovation proxy. The chunk then performs a
3452
+ single FlashAttention-2 call over [memory KV, chunk KV]. This preserves
3453
+ causal semantics, keeps the memory frozen during the chunk, and avoids a
3454
+ second attention output.
3455
+ """
3456
+ P = self.iha_P
3457
+ B, H_q, SP, d_head = q.shape
3458
+ H_kv = k.shape[1]
3459
+ q_fa_all = q.transpose(1, 2).contiguous() # [B, S*P, H_q, d]
3460
+ k_fa_all = k.transpose(1, 2).contiguous() # [B, S*P, H_kv, d]
3461
+ v_fa_all = v.transpose(1, 2).contiguous() # [B, S*P, H_kv, d]
3462
+
3463
+ scores = self._hola_value_innovation_scores(v, gate_states, S_orig) # [B, S]
3464
+ chunk = max(1, int(self.hola_chunk_size))
3465
+ mem_k = max(0, int(self.hola_memory_size))
3466
+ window_left = self._resolve_iha_window(S_orig) * P if self.iha_is_local else -1
3467
+
3468
+ outputs = []
3469
+ for start in range(0, S_orig, chunk):
3470
+ end = min(start + chunk, S_orig)
3471
+ q_cur = q_fa_all[:, start * P : end * P]
3472
+ k_cur = k_fa_all[:, start * P : end * P]
3473
+ v_cur = v_fa_all[:, start * P : end * P]
3474
+
3475
+ if mem_k > 0 and start > 0:
3476
+ k_select = min(mem_k, start)
3477
+ _, token_idx = torch.topk(scores[:, :start], k=k_select, dim=1)
3478
+ p_offsets = torch.arange(P, device=q.device, dtype=token_idx.dtype).view(1, 1, P)
3479
+ virtual_idx = (token_idx.unsqueeze(-1) * P + p_offsets).reshape(B, k_select * P)
3480
+ gather_idx = virtual_idx[:, :, None, None].expand(B, k_select * P, H_kv, d_head)
3481
+ k_mem = torch.gather(k_fa_all, dim=1, index=gather_idx).contiguous()
3482
+ v_mem = torch.gather(v_fa_all, dim=1, index=gather_idx).contiguous()
3483
+ else:
3484
+ k_mem = None
3485
+ v_mem = None
3486
+
3487
+ outputs.append(
3488
+ self._hola_augmented_fa_call(
3489
+ q_cur=q_cur,
3490
+ k_cur=k_cur,
3491
+ v_cur=v_cur,
3492
+ k_mem=k_mem,
3493
+ v_mem=v_mem,
3494
+ dropout=dropout,
3495
+ window_left=window_left,
3496
+ )
3497
+ )
3498
+
3499
+ return torch.cat(outputs, dim=1).contiguous()
3500
+
3501
  def _iha_seq_expand_flash_forward(
3502
  self,
3503
  q: torch.Tensor,
 
3510
  S_orig: int,
3511
  position_ids: Optional[torch.Tensor],
3512
  attention_mask: Optional[torch.Tensor] = None,
3513
+ gate_states: Optional[torch.Tensor] = None,
3514
  ) -> Tuple[torch.Tensor, None]:
3515
  """
3516
  Flash attention for the IHA seq-expand path (P>1).
 
3559
  dropout : attention dropout probability
3560
  S_orig : original (pre-expand) sequence length S
3561
  position_ids: [B, S] or None — for packing detection + cu_seqlens
3562
+ gate_states : [B, S, H_q*d] or None — optional Gated-Attention gate
3563
+ logits used only by the HOLA-derived eviction proxy.
3564
 
3565
  Returns
3566
  -------
 
3661
 
3662
  # ── Dense (non-packed) path ───────────────────────────────────────────
3663
  else:
3664
+ if self.use_hola_memory and self.iha_P > 1 and not use_affine:
3665
+ # HOLA-derived memory is intentionally restricted to the dense
3666
+ # direct-FA2 seq-expand branch. Packed varlen and the affine
3667
+ # inline decomposition keep the original baseline path until a
3668
+ # dedicated fused kernel exposes the exact pre-dropout residual
3669
+ # statistics.
3670
+ flash_out = self._iha_hola_seq_expand_flash_forward(
3671
+ q,
3672
+ k,
3673
+ v,
3674
+ dropout=dropout,
3675
+ S_orig=S_orig,
3676
+ gate_states=gate_states,
3677
+ )
3678
+ else:
3679
+ flash_out = flash_attn_fn(
3680
+ q_fa,
3681
+ k_fa,
3682
+ v_fa,
3683
+ dropout_p=dropout if self.training else 0.0,
3684
+ softmax_scale=self.scaling,
3685
+ causal=True,
3686
+ window_size=window,
3687
+ ) # [B, S·P, H_q, d]
3688
 
3689
  # ── Affine-Scaled Attention inline (flash path) ───────────────────────
3690
  # Decomposition used here:
 
4128
  S_orig=_S_orig,
4129
  position_ids=position_ids,
4130
  attention_mask=attention_mask,
4131
+ gate_states=gate,
4132
  )
4133
  elif use_affine:
4134
  if self.config._attn_implementation == "eager":