"""Fixed-shape Qwen3 candidate encoder and complete NanoJev decision head.""" from __future__ import annotations import math import torch from torch import nn from torch.nn import functional as F MASK = -1e4 def rotate_half(value: torch.Tensor) -> torch.Tensor: first, second = value.chunk(2, dim=-1) return torch.cat((-second, first), dim=-1) def rms_norm(hidden: torch.Tensor, norm: nn.Module, width: int) -> torch.Tensor: doubled = torch.cat((hidden, -hidden), dim=-1) normalized = F.layer_norm(doubled, (width * 2,), eps=norm.variance_epsilon) return normalized[..., :width] * norm.weight class NanoEncoder(nn.Module): """Encode the complete set of candidate paths with the trained Qwen3 body.""" def __init__(self, decision_model: nn.Module, length: int, candidates: int): super().__init__() body = decision_model.backbone config = body.config self.embed_tokens = body.embed_tokens self.layers = body.layers self.norm = body.norm self.length = length self.candidates = candidates self.heads = config.num_attention_heads self.kv_heads = config.num_key_value_heads self.groups = self.heads // self.kv_heads self.head_dim = config.head_dim self.hidden = config.hidden_size self.scale = self.head_dim**-0.5 positions = torch.arange(length).unsqueeze(0) probe = torch.zeros(1, length, config.hidden_size) cos, sin = body.rotary_emb(probe, positions) self.register_buffer("cos", cos.unsqueeze(1).detach().clone()) self.register_buffer("sin", sin.unsqueeze(1).detach().clone()) causal = torch.triu(torch.full((length, length), MASK), diagonal=1) self.register_buffer("causal", causal.view(1, 1, length, length)) def _layer(self, layer: nn.Module, hidden: torch.Tensor, mask: torch.Tensor) -> torch.Tensor: attention = layer.self_attn normalized = rms_norm(hidden, layer.input_layernorm, self.hidden) q = attention.q_proj(normalized).view(self.candidates, self.length, self.heads, self.head_dim) k = attention.k_proj(normalized).view(self.candidates, self.length, self.kv_heads, self.head_dim) v = attention.v_proj(normalized).view(self.candidates, self.length, self.kv_heads, self.head_dim) q = attention.q_norm(q).transpose(1, 2) k = attention.k_norm(k).transpose(1, 2) v = v.transpose(1, 2) q = q * self.cos + rotate_half(q) * self.sin k = k * self.cos + rotate_half(k) * self.sin k = k.repeat_interleave(self.groups, dim=1) v = v.repeat_interleave(self.groups, dim=1) weights = torch.matmul(q, k.transpose(2, 3)) * self.scale + mask weights = torch.softmax(weights.float(), dim=-1).to(q.dtype) attended = torch.matmul(weights, v).transpose(1, 2).reshape( self.candidates, self.length, self.heads * self.head_dim ) hidden = hidden + attention.o_proj(attended) return hidden + layer.mlp(rms_norm(hidden, layer.post_attention_layernorm, self.hidden)) def forward( self, input_ids: torch.Tensor, attention_mask: torch.Tensor, eos_map: torch.Tensor ) -> torch.Tensor: padding = (1.0 - attention_mask.float()).view(self.candidates, 1, 1, self.length) * MASK mask = self.causal + padding hidden = self.embed_tokens(input_ids.long()) for layer in self.layers: hidden = self._layer(layer, hidden, mask) hidden = rms_norm(hidden, self.norm, self.hidden).float() return torch.matmul(eos_map, hidden).transpose(0, 1) class NanoHead(nn.Module): """The trained scalar and set-attention heads for Choice, Boolean, and Score.""" def __init__(self, decision_model: nn.Module, candidates: int): super().__init__() self.norm = decision_model.norm self.scalar = decision_model.scalar self.set_project = decision_model.set_project self.set_attention = decision_model.set_attention self.set_output = decision_model.set_output self.candidates = candidates self.heads = 4 self.head_dim = 32 self.scale = 1.0 / math.sqrt(self.head_dim) boolean_mask = torch.zeros(1, candidates) boolean_mask[0, :2] = 1 self.register_buffer("boolean_mask", boolean_mask) def forward( self, embeddings: torch.Tensor, candidate_mask: torch.Tensor, use_set_head: torch.Tensor, is_boolean: torch.Tensor, ) -> tuple[torch.Tensor, torch.Tensor]: h = self.norm(embeddings) scalar = self.scalar(h).squeeze(-1).float() log_k = candidate_mask.sum(-1).clamp(min=1).float().log().view(1, 1, 1) log_k = log_k.expand(1, self.candidates, 1) u = self.set_project(torch.cat((h, log_k.to(h.dtype)), dim=-1)) projected = F.linear(u, self.set_attention.in_proj_weight, self.set_attention.in_proj_bias) q, k, v = projected.chunk(3, dim=-1) q = q.view(1, self.candidates, self.heads, self.head_dim).transpose(1, 2) k = k.view(1, self.candidates, self.heads, self.head_dim).transpose(1, 2) v = v.view(1, self.candidates, self.heads, self.head_dim).transpose(1, 2) scores = torch.matmul(q, k.transpose(2, 3)) * self.scale scores = scores + (1.0 - candidate_mask).view(1, 1, 1, self.candidates) * MASK mixed = torch.matmul(torch.softmax(scores.float(), dim=-1).to(v.dtype), v) mixed = mixed.transpose(1, 2).reshape(1, self.candidates, -1) mixed = self.set_attention.out_proj(mixed) delta = self.set_output(torch.tanh(u + mixed)).squeeze(-1).float() normal_logits = scalar + use_set_head * delta boolean_logits = torch.cat((scalar[:, :1] * 0, scalar[:, :1], scalar[:, 2:] * 0), dim=-1) logits = normal_logits * (1.0 - is_boolean) + boolean_logits * is_boolean output_mask = candidate_mask * (1.0 - is_boolean) + self.boolean_mask * is_boolean logits = logits * output_mask + (1.0 - output_mask) * MASK return logits, torch.softmax(logits, dim=-1)