Update modeling_neollm.py
Browse files- 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),
|
| 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 |
-
|
| 3412 |
-
|
| 3413 |
-
|
| 3414 |
-
|
| 3415 |
-
|
| 3416 |
-
|
| 3417 |
-
|
| 3418 |
-
|
| 3419 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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":
|