Download runtime/interaction_head.py from MubitLabs/banyan-sapling: direct link, hf CLI and curl.
- Browser
- Download file 4.8 kB
-
https://huggingface.co/MubitLabs/banyan-sapling/resolve/main/runtime/interaction_head.py
- Command line
-
hf download hf://MubitLabs/banyan-sapling/runtime/interaction_head.py
-
curl -L -o interaction_head.py https://huggingface.co/MubitLabs/banyan-sapling/resolve/main/runtime/interaction_head.py
4.8 kB
| """Segment-aware interactions between policy, evidence, and claim vectors.""" | |
| import torch | |
| from torch import Tensor, nn | |
| class TriVectorInteractionHead(nn.Module): | |
| """Classify policy/evidence/claim vectors using seven ordered feature blocks.""" | |
| def __init__(self, hidden_dim: int, num_classes: int = 4, dropout: float = .1): | |
| super().__init__() | |
| for name, value in (("hidden_dim", hidden_dim), ("num_classes", num_classes)): | |
| if isinstance(value, bool) or not isinstance(value, int): | |
| raise TypeError(f"{name} must be an integer") | |
| if value <= 0: | |
| raise ValueError(f"{name} must be positive") | |
| self.hidden_dim = hidden_dim | |
| self.classifier = nn.Sequential( | |
| nn.Dropout(dropout), | |
| nn.Linear(7 * hidden_dim, 2 * hidden_dim), | |
| nn.GELU(), | |
| nn.Dropout(dropout), | |
| nn.Linear(2 * hidden_dim, num_classes), | |
| ) | |
| def construct_features(self, h_P: Tensor, h_E: Tensor, h_C: Tensor) -> Tensor: | |
| """Return [P, E, C, |P-E|, P*E, |E-C|, E*C] with shape [B, 7H].""" | |
| for vector in (h_P, h_E, h_C): | |
| if not isinstance(vector, Tensor) or not vector.is_floating_point(): | |
| raise TypeError("segment vectors must be floating-point tensors") | |
| if vector.ndim != 2 or vector.shape[-1] != self.hidden_dim: | |
| raise ValueError("segment vectors must have shape [B, hidden_dim]") | |
| if vector.shape != h_P.shape or vector.dtype != h_P.dtype or vector.device != h_P.device: | |
| raise ValueError("segment vectors must share shape, dtype, and device") | |
| return torch.cat((h_P, h_E, h_C, (h_P - h_E).abs(), h_P * h_E, | |
| (h_E - h_C).abs(), h_E * h_C), dim=-1) | |
| def forward(self, h_P: Tensor, h_E: Tensor, h_C: Tensor) -> Tensor: | |
| return self.classifier(self.construct_features(h_P, h_E, h_C)) | |
| def pool_segments(self, hidden_states: Tensor, segments: Tensor, | |
| attention_mask: Tensor) -> tuple[Tensor, Tensor, Tensor]: | |
| """Mean-pool [B,L,H] states into policy (1), evidence (2), and claim (3). | |
| Segment 0 denotes delimiters/padding. Only tokens with a binary attention | |
| mask of 1 contribute. Every row must contain an unmasked token in each | |
| segment; empty segments raise ValueError rather than producing NaNs. | |
| Float16/bfloat16 sums and division use float32 to avoid overflow and | |
| count rounding; pooled vectors retain the input dtype and device. | |
| """ | |
| if not isinstance(hidden_states, Tensor) or not hidden_states.is_floating_point(): | |
| raise TypeError("hidden_states must be a floating-point tensor") | |
| if hidden_states.ndim != 3 or hidden_states.shape[-1] != self.hidden_dim: | |
| raise ValueError("hidden_states must have shape [B, L, hidden_dim]") | |
| for name, tensor in (("segments", segments), ("attention_mask", attention_mask)): | |
| if not isinstance(tensor, Tensor): | |
| raise TypeError(f"{name} must be a tensor") | |
| if tensor.shape != hidden_states.shape[:2]: | |
| raise ValueError(f"{name} must have shape [B, L]") | |
| if tensor.device != hidden_states.device: | |
| raise ValueError(f"{name} must share the hidden_states device") | |
| if segments.dtype not in (torch.uint8, torch.int8, torch.int16, torch.int32, torch.int64): | |
| raise TypeError("segments must contain integer IDs") | |
| if ((segments < 0) | (segments > 3)).any(): | |
| raise ValueError("segment IDs must be 0, 1, 2, or 3") | |
| if not ((attention_mask == 0) | (attention_mask == 1)).all(): | |
| raise ValueError("attention_mask must be binary") | |
| accumulation_dtype = (torch.float32 if hidden_states.dtype in (torch.float16, torch.bfloat16) | |
| else hidden_states.dtype) | |
| pooled = [] | |
| for segment_id in (1, 2, 3): | |
| selected = (segments == segment_id) & attention_mask.bool() | |
| counts = selected.sum(dim=1, keepdim=True) | |
| if (counts == 0).any(): | |
| raise ValueError(f"empty segment {segment_id} in at least one batch row") | |
| masked = hidden_states.masked_fill(~selected.unsqueeze(-1), 0) | |
| mean = masked.sum(dim=1, dtype=accumulation_dtype) / counts.to(accumulation_dtype) | |
| pooled.append(mean.to(hidden_states.dtype)) | |
| return tuple(pooled) | |
| def forward_from_hidden(self, hidden_states: Tensor, segments: Tensor, | |
| attention_mask: Tensor) -> Tensor: | |
| """Pool segment states and return [B, num_classes] classification logits.""" | |
| return self(*self.pool_segments(hidden_states, segments, attention_mask)) | |