KitsuVp commited on
Commit
5dcacc0
·
verified ·
1 Parent(s): 712bbc2

Update modeling_neollm.py

Browse files
Files changed (1) hide show
  1. modeling_neollm.py +272 -26
modeling_neollm.py CHANGED
@@ -5,8 +5,9 @@ full attention augmented with optional Momentum, MEA, and LUCID operators,
5
  Gated Attention (Qiu et al., 2025) combined with Affine-Scaled Attention
6
  (Bae et al., 2026), an optional Leviathan continuous token embedding
7
  generator, an optional Leviathan-JTok-M token-indexed modulation module,
8
- optional Spelling Bee Embeddings (Rabe et al., 2026), and optional Context
9
- Re-Positioning (Li et al., 2026).
 
10
 
11
  Attention stack (orthogonal, all active simultaneously when enabled):
12
  1. Gated Attention (use_gated_attention implicit via q_proj gate chunk):
@@ -55,6 +56,8 @@ References:
55
  Embeddings for Language Modeling." arXiv:2601.18030.
56
  Context Re-Positioning: Li, Zhao, Cai & Sproat (2026). "REPO: Language
57
  Models with Context Re-Positioning." arXiv:2512.14391.
 
 
58
  """
59
 
60
  import math
@@ -243,6 +246,28 @@ class REPOAnalysis:
243
  r_repr: Optional[torch.Tensor] = None # r [B, S, d_p] — shared repr
244
 
245
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  @dataclass
247
  class AttentionAnalysis:
248
  """
@@ -325,6 +350,9 @@ class AttentionAnalysis:
325
  # ── REPO position prediction (conditional on use_repo) ────────────
326
  repo: Optional["REPOAnalysis"] = None # None when layer_idx < repo_start_layer
327
 
 
 
 
328
  # ── IHA pseudo-head mixing (conditional on use_iha) ──────────────
329
  iha: Optional["IHAAnalysis"] = None # None when use_iha=False
330
 
@@ -2272,13 +2300,201 @@ def _apply_repo_rope(
2272
  return q_embed, k_embed
2273
 
2274
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2275
  class NeoLLMAttention(nn.Module):
2276
  """
2277
  Full attention with FANformer, SeeDNorm, ResFormer, Learnable Multipliers,
2278
  optional Momentum, MEA head-level composition, optional LUCID preconditioning,
2279
  optional Affine-Scaled Attention, optional Exclusive Self Attention,
2280
- optional Directional Routing (Taylor, 2026), and optional Context
2281
- Re-Positioning (Li et al., 2026).
 
2282
 
2283
  Directional Routing inserts at position C — post-XSA, pre-reshape — where
2284
  the output is already normalized (MEAHeadSeeDNorm) and has auto-position
@@ -2286,8 +2502,8 @@ class NeoLLMAttention(nn.Module):
2286
  orthogonal to the self-position already cleaned by XSA.
2287
 
2288
  Pipeline (all active simultaneously when enabled):
2289
- FANLayer → q_proj(gate) → q_norm/k_norm → REPO/RoPE → Momentum
2290
- → MEA(K,V) → LUCID(V) → v_ref → Affine-Scaled SDPA
2291
  → MEAHeadSeeDNorm → XSA → Directional Routing → reshape
2292
  → o_proj · sigmoid(gate) → dropout
2293
 
@@ -2495,15 +2711,19 @@ class NeoLLMAttention(nn.Module):
2495
  self.direction_vecs = None
2496
  self.direction_router = None
2497
 
2498
- # ── Context Re-Positioning (Li et al., 2026) ─────────────────────
2499
- # Active for layers at or above repo_start_layer only.
2500
- # Layers below repo_start_layer use standard integer RoPE positions.
2501
- # inv_freq is accessed from the model's rotary_emb at forward time;
2502
- # stored here as a non-persistent buffer reference set by NeoLLMModel.
 
 
2503
  self.use_repo = (
2504
- getattr(config, "use_repo", False)
2505
- and layer_idx >= getattr(config, "repo_start_layer", config.num_hidden_layers // 3)
2506
  )
 
 
2507
  if self.use_repo:
2508
  _d_p = getattr(config, "repo_d_p", config.hidden_size // 8)
2509
  self.repo_module = REPOModule(
@@ -2514,6 +2734,17 @@ class NeoLLMAttention(nn.Module):
2514
  else:
2515
  self.repo_module = None
2516
 
 
 
 
 
 
 
 
 
 
 
 
2517
  # ── Interleaved Head Attention (Duvvuri et al., 2026) ─────────────────
2518
  # Implementa cross-head mixing antes de RoPE: cada pseudo-head h,p
2519
  # es combinación lineal aprendida de los H input-heads originales.
@@ -3294,24 +3525,34 @@ class NeoLLMAttention(nn.Module):
3294
  # no meta-tensor issue on lm_eval / to(device) paths.
3295
  # (Li et al., 2026, §3.2 — Eq. 6–7)
3296
  repo_a = attn_analysis.repo if attn_analysis is not None else None
 
3297
  z = self.repo_module(hidden_states, repo_analysis=repo_a) # [B, H, S]
 
 
3298
  inv_freq, attn_scaling = repo_rope_args
3299
  if _iha_seq_expand:
3300
- # IHA seq-expand + REPO: each real position s has P virtual slots.
3301
- # Assign continuous REPO position z[b,h,s]·P + p to slot (s,p),
3302
- # scaling z by P to create room for P sub-positions while preserving
3303
- # REPO's relative ordering across tokens.
3304
- # _apply_repo_rope handles the flat S·P sequence correctly when
3305
- # H_q_eff == H_repo (P_derived=1 inside the function).
3306
  P = self.iha_P
3307
  p_offsets = torch.arange(P, device=z.device, dtype=z.dtype)
3308
  # z[B,H,S] → [B,H,S,1]*P + [1,1,1,P] → [B,H,S,P] → [B,H,S·P]
3309
  z_expanded = (z.unsqueeze(-1) * P + p_offsets.view(1, 1, 1, P)
3310
  ).reshape(z.shape[0], z.shape[1], _S_orig * P)
3311
- q, k = _apply_repo_rope(q, k, z_expanded, inv_freq, attn_scaling)
 
 
 
 
 
3312
  else:
3313
  # Standard REPO path (P=1 or no IHA): z covers original S positions.
3314
- q, k = _apply_repo_rope(q, k, z, inv_freq, attn_scaling)
 
 
 
 
 
3315
  elif _iha_seq_expand:
3316
  # IHA seq-expand + standard integer RoPE:
3317
  # Build interleaved positions pos_iha[s·P+p] = position_ids[s]·P + p
@@ -5115,18 +5356,22 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
5115
  Called once per layer per forward when analysis is active.
5116
  """
5117
  cfg = self.config
 
 
5118
  _repo_active = (
5119
- getattr(cfg, "use_repo", False)
5120
  and layer_idx >= getattr(cfg, "repo_start_layer", cfg.num_hidden_layers // 3)
5121
  )
 
5122
  _versatile = getattr(cfg, "use_versatile_ffn", False)
5123
  return LayerAnalysis(
5124
  seednorm_pre_attn = SeeDNormAnalysis(),
5125
  seednorm_post_attn = SeeDNormAnalysis(),
5126
  attention = AttentionAnalysis(
5127
- fan = FANAnalysis(),
5128
- hadamard = HadamardAnalysis() if getattr(cfg, "use_hadamard_o_proj", False) else None,
5129
- repo = REPOAnalysis() if _repo_active else None,
 
5130
  ),
5131
  mlp = MLPAnalysis(
5132
  fan = FANAnalysis() if not _versatile else None,
@@ -5250,9 +5495,10 @@ class NeoLLMModel(NeoLLMPreTrainedModel):
5250
  getattr(self.config, "use_iha", False)
5251
  and int(getattr(self.config, "iha_num_pseudo_heads", 1)) > 1
5252
  )
 
5253
  repo_rope_args = (
5254
  (self.rotary_emb.inv_freq, self.rotary_emb.attention_scaling)
5255
- if (getattr(self.config, "use_repo", False) or _iha_needs_inv_freq)
5256
  else None
5257
  )
5258
 
 
5
  Gated Attention (Qiu et al., 2025) combined with Affine-Scaled Attention
6
  (Bae et al., 2026), an optional Leviathan continuous token embedding
7
  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), and optional REPO-GRAPE contextual group
10
+ positioning (Li et al., 2026 + Zhang et al., 2026).
11
 
12
  Attention stack (orthogonal, all active simultaneously when enabled):
13
  1. Gated Attention (use_gated_attention implicit via q_proj gate chunk):
 
56
  Embeddings for Language Modeling." arXiv:2601.18030.
57
  Context Re-Positioning: Li, Zhao, Cai & Sproat (2026). "REPO: Language
58
  Models with Context Re-Positioning." arXiv:2512.14391.
59
+ GRAPE: Zhang et al. (2026). "Group Representational Position Encoding."
60
+ arXiv:2512.07805 / ICLR 2026.
61
  """
62
 
63
  import math
 
246
  r_repr: Optional[torch.Tensor] = None # r [B, S, d_p] — shared repr
247
 
248
 
249
+
250
+ @dataclass
251
+ class RepoGrapeAnalysis:
252
+ """
253
+ Internals of the REPO-GRAPE positional operator.
254
+ Populated only when config.use_repo_grape=True and the current layer is
255
+ at or above repo_start_layer.
256
+
257
+ Minimal operator kept from the proposal:
258
+
259
+ u_i^(h) = z_i^(h)
260
+ theta_{h,r} = inv_freq_r * exp(s_{h,r})
261
+ logit_ij += q_i^T exp((u_j-u_i) Omega_h) k_j / sqrt(d)
262
+
263
+ The implementation uses canonical commuting rotary planes, learns the
264
+ per-head/per-plane angular spectrum, rotates Q/K once, and then calls the
265
+ existing attention backend.
266
+ """
267
+ positions: Optional[torch.Tensor] = None # [B,H,S] u_i = z_i
268
+ freq: Optional[torch.Tensor] = None # [H,rotary_dim/2] theta_{h,r}
269
+
270
+
271
  @dataclass
272
  class AttentionAnalysis:
273
  """
 
350
  # ── REPO position prediction (conditional on use_repo) ────────────
351
  repo: Optional["REPOAnalysis"] = None # None when layer_idx < repo_start_layer
352
 
353
+ # ── REPO-GRAPE contextual group positioning (conditional on use_repo_grape)
354
+ repo_grape: Optional["RepoGrapeAnalysis"] = None
355
+
356
  # ── IHA pseudo-head mixing (conditional on use_iha) ──────────────
357
  iha: Optional["IHAAnalysis"] = None # None when use_iha=False
358
 
 
2300
  return q_embed, k_embed
2301
 
2302
 
2303
+
2304
+ class RepoGrapePositioning(nn.Module):
2305
+ r"""
2306
+ Minimal REPO-GRAPE-M positional operator.
2307
+
2308
+ This module intentionally keeps only the specific operator from the
2309
+ proposal:
2310
+
2311
+ 1. REPO predicts contextual coordinates z_i^(h)=f_phi^(h)(h_i).
2312
+ 2. REPO-GRAPE uses u_i^(h)=z_i^(h), with no extra position mode.
2313
+ 3. GRAPE-M applies canonical commuting SO(d) rotary planes to Q/K.
2314
+ 4. Each query head learns its own angular spectrum:
2315
+
2316
+ theta_{h,r}=inv_freq_r*exp(s_{h,r}).
2317
+
2318
+ Therefore
2319
+
2320
+ (G_h(u_i)q_i)^T(G_h(u_j)k_j)
2321
+ = q_i^T G_h(u_j-u_i) k_j,
2322
+
2323
+ preserving the relative GRAPE law while staying compatible with the
2324
+ existing attention backend.
2325
+ """
2326
+
2327
+ def __init__(
2328
+ self,
2329
+ config: NeoLLMConfig,
2330
+ layer_idx: int,
2331
+ num_attention_heads: int,
2332
+ num_key_value_heads: int,
2333
+ head_dim: int,
2334
+ ):
2335
+ super().__init__()
2336
+ self.config = config
2337
+ self.layer_idx = layer_idx
2338
+ self.num_attention_heads = int(num_attention_heads)
2339
+ self.num_key_value_heads = int(num_key_value_heads)
2340
+ self.head_dim = int(head_dim)
2341
+ self.max_rot_half = self.head_dim // 2
2342
+
2343
+ # The single REPO-GRAPE parameter: a learned log-scale over the
2344
+ # base RoPE/GRAPE frequencies, per query head and rotary plane.
2345
+ self.freq_log_scale = nn.Parameter(
2346
+ torch.zeros(self.num_attention_heads, self.max_rot_half, dtype=torch.float32),
2347
+ requires_grad=True,
2348
+ )
2349
+
2350
+ def _apply(self, fn):
2351
+ # Keep the small spectral parameters in fp32 under model.to(dtype).
2352
+ super()._apply(fn)
2353
+ p = self.freq_log_scale
2354
+ p.data = p.data.float()
2355
+ if p.grad is not None:
2356
+ p.grad.data = p.grad.data.float()
2357
+ return self
2358
+
2359
+ def transform_positions(
2360
+ self,
2361
+ z: torch.Tensor,
2362
+ analysis: Optional[RepoGrapeAnalysis] = None,
2363
+ ) -> torch.Tensor:
2364
+ """Use the REPO coordinate directly: u_i^(h)=z_i^(h)."""
2365
+ if analysis is not None:
2366
+ analysis.positions = z.detach()
2367
+ return z
2368
+
2369
+ def _query_freq(
2370
+ self,
2371
+ inv_freq: torch.Tensor,
2372
+ H_repo: int,
2373
+ analysis: Optional[RepoGrapeAnalysis],
2374
+ ) -> torch.Tensor:
2375
+ rot_half = int(inv_freq.shape[0])
2376
+ if rot_half > self.max_rot_half:
2377
+ raise ValueError(
2378
+ f"REPO-GRAPE rotary_dim/2={rot_half} exceeds head_dim/2={self.max_rot_half}."
2379
+ )
2380
+ if H_repo != self.num_attention_heads:
2381
+ raise ValueError(
2382
+ f"REPO-GRAPE expected H_repo={self.num_attention_heads}, got {H_repo}."
2383
+ )
2384
+
2385
+ base = inv_freq.float().view(1, rot_half)
2386
+ log_scale = self.freq_log_scale[:, :rot_half].float()
2387
+ freq = base * torch.exp(log_scale)
2388
+ freq = freq.to(device=inv_freq.device)
2389
+
2390
+ if analysis is not None:
2391
+ analysis.freq = freq.detach()
2392
+ return freq
2393
+
2394
+ def apply_multiplicative(
2395
+ self,
2396
+ q: torch.Tensor,
2397
+ k: torch.Tensor,
2398
+ z: torch.Tensor,
2399
+ inv_freq: torch.Tensor,
2400
+ attention_scaling: float,
2401
+ analysis: Optional[RepoGrapeAnalysis] = None,
2402
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
2403
+ """
2404
+ Apply REPO-GRAPE-M rotation to Q/K using contextual REPO coordinates.
2405
+
2406
+ Args:
2407
+ q: [B, H_q_eff, S, head_dim]
2408
+ k: [B, H_k_eff, S, head_dim]
2409
+ z: [B, H_q_base, S]
2410
+ inv_freq: [rotary_dim/2]
2411
+ attention_scaling: scaling from NeoLLMRotaryEmbedding
2412
+ """
2413
+ B, H_repo, S = z.shape
2414
+ H_q_eff = q.shape[1]
2415
+ H_k_eff = k.shape[1]
2416
+
2417
+ if H_q_eff % H_repo != 0:
2418
+ raise ValueError(
2419
+ f"REPO-GRAPE expected q heads divisible by z heads, "
2420
+ f"got H_q={H_q_eff} vs H_repo={H_repo}."
2421
+ )
2422
+ P = H_q_eff // H_repo
2423
+
2424
+ if H_k_eff % max(P, 1) != 0:
2425
+ raise ValueError(
2426
+ f"REPO-GRAPE expected k heads divisible by pseudo factor P, "
2427
+ f"got H_k={H_k_eff} vs P={P}."
2428
+ )
2429
+
2430
+ z_q_struct = z.unsqueeze(2).expand(B, H_repo, P, S) # [B,H_q_base,P,S]
2431
+ H_k_base = H_k_eff // max(P, 1)
2432
+ if H_repo % H_k_base != 0:
2433
+ raise ValueError(
2434
+ f"REPO-GRAPE expected original query heads divisible by key groups, "
2435
+ f"got H_repo={H_repo} vs H_k_base={H_k_base}."
2436
+ )
2437
+
2438
+ q_per_k_base = H_repo // H_k_base
2439
+ z_q = z_q_struct.reshape(B, H_q_eff, S)
2440
+ z_k = (
2441
+ z_q_struct
2442
+ .view(B, H_k_base, q_per_k_base, P, S)
2443
+ .mean(dim=2)
2444
+ .reshape(B, H_k_eff, S)
2445
+ )
2446
+
2447
+ rot_half = int(inv_freq.shape[0])
2448
+ rotary_dim = rot_half * 2
2449
+
2450
+ freq_repo = self._query_freq(inv_freq, H_repo, analysis=analysis)
2451
+ freq_q_struct = freq_repo.unsqueeze(1).expand(H_repo, P, rot_half)
2452
+ freq_q = freq_q_struct.reshape(H_q_eff, rot_half)
2453
+ freq_k = (
2454
+ freq_q_struct
2455
+ .view(H_k_base, q_per_k_base, P, rot_half)
2456
+ .mean(dim=1)
2457
+ .reshape(H_k_eff, rot_half)
2458
+ )
2459
+
2460
+ freqs_q = z_q.float().unsqueeze(-1) * freq_q.to(device=q.device).view(
2461
+ 1, H_q_eff, 1, rot_half
2462
+ )
2463
+ freqs_k = z_k.float().unsqueeze(-1) * freq_k.to(device=k.device).view(
2464
+ 1, H_k_eff, 1, rot_half
2465
+ )
2466
+
2467
+ emb_q = torch.cat([freqs_q, freqs_q], dim=-1)
2468
+ emb_k = torch.cat([freqs_k, freqs_k], dim=-1)
2469
+
2470
+ cos_q = (emb_q.cos() * attention_scaling).to(q.dtype)
2471
+ sin_q = (emb_q.sin() * attention_scaling).to(q.dtype)
2472
+ cos_k = (emb_k.cos() * attention_scaling).to(k.dtype)
2473
+ sin_k = (emb_k.sin() * attention_scaling).to(k.dtype)
2474
+
2475
+ q_rot, q_pass = q[..., :rotary_dim], q[..., rotary_dim:]
2476
+ k_rot, k_pass = k[..., :rotary_dim], k[..., rotary_dim:]
2477
+
2478
+ q_embed = torch.cat(
2479
+ [(q_rot * cos_q) + (rotate_half(q_rot) * sin_q), q_pass],
2480
+ dim=-1,
2481
+ )
2482
+ k_embed = torch.cat(
2483
+ [(k_rot * cos_k) + (rotate_half(k_rot) * sin_k), k_pass],
2484
+ dim=-1,
2485
+ )
2486
+ return q_embed, k_embed
2487
+
2488
+
2489
+
2490
  class NeoLLMAttention(nn.Module):
2491
  """
2492
  Full attention with FANformer, SeeDNorm, ResFormer, Learnable Multipliers,
2493
  optional Momentum, MEA head-level composition, optional LUCID preconditioning,
2494
  optional Affine-Scaled Attention, optional Exclusive Self Attention,
2495
+ optional Directional Routing (Taylor, 2026), optional Context
2496
+ Re-Positioning (Li et al., 2026), and optional REPO-GRAPE contextual group
2497
+ positioning (Li et al., 2026 + Zhang et al., 2026).
2498
 
2499
  Directional Routing inserts at position C — post-XSA, pre-reshape — where
2500
  the output is already normalized (MEAHeadSeeDNorm) and has auto-position
 
2502
  orthogonal to the self-position already cleaned by XSA.
2503
 
2504
  Pipeline (all active simultaneously when enabled):
2505
+ FANLayer → q_proj(gate) → q_norm/k_norm → REPO/RoPE or REPO-GRAPE-M
2506
+ → Momentum → MEA(K,V) → LUCID(V) → v_ref → Affine-Scaled SDPA
2507
  → MEAHeadSeeDNorm → XSA → Directional Routing → reshape
2508
  → o_proj · sigmoid(gate) → dropout
2509
 
 
2711
  self.direction_vecs = None
2712
  self.direction_router = None
2713
 
2714
+ # ── Context Re-Positioning / REPO-GRAPE ─────────────────────────────
2715
+ # REPO-GRAPE reuses the REPO coordinate module f_phi and replaces only
2716
+ # the positional action applied to Q/K. Enabling use_repo_grape=True
2717
+ # therefore activates the REPO coordinate path even when use_repo=False.
2718
+ _repo_requested = bool(getattr(config, "use_repo", False))
2719
+ _repo_grape_requested = bool(getattr(config, "use_repo_grape", False))
2720
+ _repo_start_layer = getattr(config, "repo_start_layer", config.num_hidden_layers // 3)
2721
  self.use_repo = (
2722
+ (_repo_requested or _repo_grape_requested)
2723
+ and layer_idx >= _repo_start_layer
2724
  )
2725
+ self.use_repo_grape = _repo_grape_requested and self.use_repo
2726
+
2727
  if self.use_repo:
2728
  _d_p = getattr(config, "repo_d_p", config.hidden_size // 8)
2729
  self.repo_module = REPOModule(
 
2734
  else:
2735
  self.repo_module = None
2736
 
2737
+ if self.use_repo_grape:
2738
+ self.repo_grape = RepoGrapePositioning(
2739
+ config=config,
2740
+ layer_idx=layer_idx,
2741
+ num_attention_heads=config.num_attention_heads,
2742
+ num_key_value_heads=config.num_key_value_heads,
2743
+ head_dim=self.head_dim,
2744
+ )
2745
+ else:
2746
+ self.repo_grape = None
2747
+
2748
  # ── Interleaved Head Attention (Duvvuri et al., 2026) ─────────────────
2749
  # Implementa cross-head mixing antes de RoPE: cada pseudo-head h,p
2750
  # es combinación lineal aprendida de los H input-heads originales.
 
3525
  # no meta-tensor issue on lm_eval / to(device) paths.
3526
  # (Li et al., 2026, §3.2 — Eq. 6–7)
3527
  repo_a = attn_analysis.repo if attn_analysis is not None else None
3528
+ repo_grape_a = attn_analysis.repo_grape if attn_analysis is not None else None
3529
  z = self.repo_module(hidden_states, repo_analysis=repo_a) # [B, H, S]
3530
+ if self.use_repo_grape:
3531
+ z = self.repo_grape.transform_positions(z, analysis=repo_grape_a)
3532
  inv_freq, attn_scaling = repo_rope_args
3533
  if _iha_seq_expand:
3534
+ # IHA seq-expand + REPO/REPO-GRAPE: each real position s has P virtual slots.
3535
+ # Assign continuous position z[b,h,s]·P + p to slot (s,p), scaling z by P
3536
+ # to create room for P sub-positions while preserving relative ordering.
 
 
 
3537
  P = self.iha_P
3538
  p_offsets = torch.arange(P, device=z.device, dtype=z.dtype)
3539
  # z[B,H,S] → [B,H,S,1]*P + [1,1,1,P] → [B,H,S,P] → [B,H,S·P]
3540
  z_expanded = (z.unsqueeze(-1) * P + p_offsets.view(1, 1, 1, P)
3541
  ).reshape(z.shape[0], z.shape[1], _S_orig * P)
3542
+ if self.use_repo_grape:
3543
+ q, k = self.repo_grape.apply_multiplicative(
3544
+ q, k, z_expanded, inv_freq, attn_scaling, analysis=repo_grape_a
3545
+ )
3546
+ else:
3547
+ q, k = _apply_repo_rope(q, k, z_expanded, inv_freq, attn_scaling)
3548
  else:
3549
  # Standard REPO path (P=1 or no IHA): z covers original S positions.
3550
+ if self.use_repo_grape:
3551
+ q, k = self.repo_grape.apply_multiplicative(
3552
+ q, k, z, inv_freq, attn_scaling, analysis=repo_grape_a
3553
+ )
3554
+ else:
3555
+ q, k = _apply_repo_rope(q, k, z, inv_freq, attn_scaling)
3556
  elif _iha_seq_expand:
3557
  # IHA seq-expand + standard integer RoPE:
3558
  # Build interleaved positions pos_iha[s·P+p] = position_ids[s]·P + p
 
5356
  Called once per layer per forward when analysis is active.
5357
  """
5358
  cfg = self.config
5359
+ _repo_requested = bool(getattr(cfg, "use_repo", False))
5360
+ _repo_grape_requested = bool(getattr(cfg, "use_repo_grape", False))
5361
  _repo_active = (
5362
+ (_repo_requested or _repo_grape_requested)
5363
  and layer_idx >= getattr(cfg, "repo_start_layer", cfg.num_hidden_layers // 3)
5364
  )
5365
+ _repo_grape_active = _repo_grape_requested and _repo_active
5366
  _versatile = getattr(cfg, "use_versatile_ffn", False)
5367
  return LayerAnalysis(
5368
  seednorm_pre_attn = SeeDNormAnalysis(),
5369
  seednorm_post_attn = SeeDNormAnalysis(),
5370
  attention = AttentionAnalysis(
5371
+ fan = FANAnalysis(),
5372
+ hadamard = HadamardAnalysis() if getattr(cfg, "use_hadamard_o_proj", False) else None,
5373
+ repo = REPOAnalysis() if _repo_active else None,
5374
+ repo_grape = RepoGrapeAnalysis() if _repo_grape_active else None,
5375
  ),
5376
  mlp = MLPAnalysis(
5377
  fan = FANAnalysis() if not _versatile else None,
 
5495
  getattr(self.config, "use_iha", False)
5496
  and int(getattr(self.config, "iha_num_pseudo_heads", 1)) > 1
5497
  )
5498
+ _repo_grape_needs_inv_freq = bool(getattr(self.config, "use_repo_grape", False))
5499
  repo_rope_args = (
5500
  (self.rotary_emb.inv_freq, self.rotary_emb.attention_scaling)
5501
+ if (getattr(self.config, "use_repo", False) or _repo_grape_needs_inv_freq or _iha_needs_inv_freq)
5502
  else None
5503
  )
5504