loopformer / scripts /recurrent_qwen /partitioned.py
mukunds's picture
Publish frozen LoopFormer pointer checkpoint and inference code
e29f88c verified
Raw History Blame Contribute Delete
9.1 kB
"""Inference-only causal factorization for count-dependent historical models.
Identical prompt prefixes are computed once per graph, at *each* loop for
full-sequence recurrence. Count-specific suffixes remain separate continuous
states. Fixed-prompt recurrence retains first-pass layer K/V, replacing only
the working position. No symbols, labels, depth values or stops enter R.
"""
from dataclasses import dataclass
import torch
from torch import Tensor, nn
from transformers.models.qwen2.modeling_qwen2 import apply_rotary_pos_emb, eager_attention_forward
from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
from .prefix import layer_compute
def suffix_layer(layer: nn.Module, hidden: Tensor, embeddings: tuple[Tensor, Tensor],
mask: Tensor, prefix: tuple[Tensor, Tensor],
replace_positions: Tensor | None = None) -> tuple[Tensor, Tensor, Tensor]:
"""Queries [B,Q,H], rotary K/V prefix [B,KV,P,D]; full additive query mask."""
attention = layer.self_attn
x = layer.input_layernorm(hidden)
shape = (*x.shape[:-1], -1, attention.head_dim)
q = attention.q_proj(x).view(shape).transpose(1, 2)
k = attention.k_proj(x).view(shape).transpose(1, 2)
v = attention.v_proj(x).view(shape).transpose(1, 2)
q, k = apply_rotary_pos_emb(q, k, *embeddings)
if replace_positions is None:
k, v = torch.cat((prefix[0], k), -2), torch.cat((prefix[1], v), -2)
else:
# Work on fresh tensors: original fixed memory is immutable across loops.
k_full, v_full = prefix[0].clone(), prefix[1].clone()
rows = torch.arange(len(hidden), device=hidden.device)
k_full[rows, :, replace_positions, :] = k[:, :, 0, :]
v_full[rows, :, replace_positions, :] = v[:, :, 0, :]
k, v = k_full, v_full
interface = ALL_ATTENTION_FUNCTIONS.get_interface(attention.config._attn_implementation, eager_attention_forward)
output, _ = interface(attention, q, k, v, mask,
dropout=0.0, scaling=attention.scaling, sliding_window=None)
output = output.reshape(*x.shape[:-1], -1).contiguous()
result = hidden + attention.o_proj(output)
result = result + layer.mlp(layer.post_attention_layernorm(result))
return result, k, v
@dataclass
class PartitionedResult:
"""Readouts [graph*count, loop, symbol], stop logits or None, final R working."""
scores: Tensor
stops: Tensor | None
working: Tensor
@torch.inference_mode()
def partitioned_forward(model: nn.Module, prefix_ids: Tensor, suffix_ids: Tensor,
suffix_mask: Tensor, graph_indices: Tensor,
token_ids: list[int], loops: int, horizons: Tensor | None = None) -> PartitionedResult:
"""Exact causal factorization, modulo documented FP32 kernel roundoff.
Prefix [G,P] has no padding, suffix [B,S] is right padded. Each suffix maps
to one prefix via [B] graph_indices. Positions match the dense concatenation.
Supports historical full_sequence/fixed_prompt models, without routed inputs,
bridges, direct heads or private recurrent controllers. Never modifies model.
"""
if (model.training or model.router is not None or model.bridge is not None or
model.state_head is not None or loops < 1):
raise ValueError('Partitioned path requires an unmodified historical eval model')
if prefix_ids.ndim != 2 or suffix_ids.shape != suffix_mask.shape or suffix_ids.ndim != 2:
raise ValueError('Expected prefix and aligned suffix token matrices')
if (not suffix_mask.any(-1).all() or not torch.equal(suffix_mask.long().cumprod(-1), suffix_mask.long())
or graph_indices.shape != (len(suffix_ids),) or
(graph_indices < 0).any() or (graph_indices >= len(prefix_ids)).any()):
raise ValueError('Need right-padded suffixes and valid graph indices')
prefix = model.embed_tokens(prefix_ids)
suffix = model.embed_tokens(suffix_ids)
device, dtype = prefix.device, prefix.dtype
p, s = prefix.shape[1], suffix.shape[1]
pp = torch.arange(p, device=device)[None]
sp = torch.arange(p, p + s, device=device)[None]
pe, se = model.rotary_emb(prefix, pp), model.rotary_emb(suffix, sp)
negative = torch.finfo(dtype).min
pm = torch.zeros(p, p, device=device, dtype=dtype).masked_fill(
torch.arange(p, device=device)[None] > torch.arange(p, device=device)[:, None], negative)[None, None]
keep = torch.cat((torch.ones(len(suffix), p, device=device, dtype=torch.bool), suffix_mask.bool()), -1)
allowed = (torch.arange(p + s, device=device)[None] <= sp[0, :, None])[None] & keep[:, None, :]
sm = torch.zeros_like(allowed, dtype=dtype).masked_fill(~allowed, negative)[:, None]
ends = suffix_mask.sum(-1).long() - 1
rows = torch.arange(len(suffix), device=device)
am = sm[rows, :, ends, :].unsqueeze(2)
ae = tuple(e.expand(len(suffix), -1, -1)[rows, ends, :].unsqueeze(1) for e in se)
def block(layers, ph, sh, record=False):
memory = []
for layer in layers:
ph, k, v = layer_compute(layer, ph, pe, pm)
sh, keys, values = suffix_layer(layer, sh, se, sm,
(k.index_select(0, graph_indices), v.index_select(0, graph_indices)))
if record:
memory.append((keys, values))
return ph, sh, memory
def working_block(layers, state, memory):
for layer, kv in zip(layers, memory, strict=True):
state, _, _ = suffix_layer(layer, state, ae, am, kv, ends + p)
return state
prefix, suffix, _ = block(model.prelude.layers, prefix, suffix)
fixed = model.recurrence_mode == 'fixed_prompt'
scores, stops = [], []
original_batch = len(suffix_ids)
active_rows = torch.arange(original_batch, device=device)
crossed = torch.zeros(original_batch, device=device, dtype=torch.bool)
if horizons is not None and (horizons.shape != (original_batch,) or (horizons < 1).any() or (horizons > loops).any()):
raise ValueError('Requested readout horizons must fit the loop cap')
r_memory = c_memory = None
working = None
selected = model.lm_head.weight[token_ids]
for loop in range(loops):
if fixed and loop:
working = working_block(model.recurrent.layers, working, r_memory)
decoded = working_block(model.coda.layers, working, c_memory)
else:
prefix, suffix, r_memory = block(model.recurrent.layers, prefix, suffix, fixed)
working = suffix[rows, ends, :].unsqueeze(1)
_, decoded_suffix, c_memory = block(model.coda.layers, prefix, suffix, fixed)
decoded = decoded_suffix[rows, ends, :].unsqueeze(1)
score = nn.functional.linear(model.norm(decoded[:, 0]), selected).float()
if not torch.isfinite(score).all():
raise FloatingPointError('Nonfinite partitioned readout')
full_score = score.new_zeros(original_batch, score.shape[-1])
full_score[active_rows] = score
scores.append(full_score)
if model.completion_head is not None:
stop = model.completion_head(working[:, 0]).float()
if not torch.isfinite(stop).all():
raise FloatingPointError('Nonfinite partitioned completion head')
full_stop = stop.new_full((original_batch,), float('-inf'))
full_stop[active_rows] = stop
stops.append(full_stop)
crossed[active_rows] |= stop >= 0
if horizons is not None and loop + 1 < loops:
need = horizons[active_rows] > loop + 1
if model.completion_head is not None:
need |= ~crossed[active_rows]
# A CUDA boolean index discovers its output shape on the host.
# Discover the same ordered rows once, then reuse them for every
# state/cache tensor instead of repeating that synchronization.
kept = need.nonzero().flatten()
if not kept.numel():
for _ in range(loop + 1, loops):
scores.append(full_score.new_zeros(full_score.shape))
if stops: stops.append(full_stop.new_full(full_stop.shape, float('-inf')))
break
active_rows = active_rows.index_select(0, kept)
graph_indices = graph_indices.index_select(0, kept)
suffix = suffix.index_select(0, kept)
working = working.index_select(0, kept)
ends = ends.index_select(0, kept)
rows = torch.arange(len(active_rows), device=device)
sm = sm.index_select(0, kept); am = am.index_select(0, kept)
ae = tuple(e.index_select(0, kept) for e in ae)
if fixed:
r_memory = [(key.index_select(0, kept), value.index_select(0, kept)) for key, value in r_memory]
c_memory = [(key.index_select(0, kept), value.index_select(0, kept)) for key, value in c_memory]
return PartitionedResult(torch.stack(scores, 1), torch.stack(stops, 1) if stops else None, working[:, 0])