aloe-arch / pooler.py
rmaser's picture
Upload ALOE codebase
e5f134b verified
Raw
History Blame Contribute Delete
5.08 kB
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
import torch
import torch.nn as nn
from transformers.modeling_attn_mask_utils import _prepare_4d_attention_mask
from .bcos_core import AloeMultiHeadAttentionPooler, NoBiasDetachableLayerNorm, select_bcos_unnormed_linear
from .layers import AloeMLP
if TYPE_CHECKING:
from .configuration_aloe_vision import AloeVisionConfig
def build_aloe_pooler(config: AloeVisionConfig) -> nn.Module | None:
"""
Build the pooler head from ``config.aloe_pooler_type``.
Returns ``None`` for ``"none"`` (backbone-only use, no pooling needed).
"""
pt = config.aloe_pooler_type
if pt == "none":
return None
if pt == "cls_token":
return AloeClsTokenPooler(config)
if pt == "multihead_attention":
return AloeSiglip2MultiheadAttentionPoolingHead(config)
if pt == "mean":
return AloeMeanPooler(config)
raise ValueError(f"Unknown aloe_pooler_type: {pt!r}")
class AloeClsTokenPooler(nn.Module):
"""
Extract the CLS token as the pooled representation.
Layout is ``[CLS, register_*, patches]`` (HF DINOv3 order), so CLS is always
sequence index ``0`` when ``aloe_cls_token`` is enabled.
"""
def __init__(self, config: AloeVisionConfig) -> None:
super().__init__()
if not config.aloe_cls_token:
raise ValueError("aloe_pooler_type='cls_token' requires config.aloe_cls_token=True")
self.cls_index = 0
def forward(
self,
hidden_state: torch.Tensor,
attention_mask: Optional[torch.Tensor] = None,
output_attentions: bool = False,
) -> torch.Tensor:
"""Return the CLS row as the pooled embedding."""
del output_attentions
return hidden_state[:, self.cls_index]
class AloeMeanPooler(nn.Module):
"""Mean-pool over patch tokens, skipping CLS and register prefix."""
def __init__(self, config: AloeVisionConfig) -> None:
super().__init__()
self._skip_prefix = (1 if config.aloe_cls_token else 0) + config.aloe_num_registers
def forward(
self,
hidden_state: torch.Tensor,
attention_mask: Optional[torch.Tensor] = None,
output_attentions: bool = False,
) -> torch.Tensor:
"""Mean over patch tokens only (strip ``[CLS, register_*]`` prefix)."""
del output_attentions
return hidden_state[:, self._skip_prefix :, :].mean(dim=1)
class AloeSiglip2MultiheadAttentionPoolingHead(nn.Module):
"""
Learned-probe attention pooler using the self-contained
:class:`AloeMultiHeadAttentionPooler` (B-cos out-projection).
"""
def __init__(self, config: AloeVisionConfig) -> None:
super().__init__()
self.probe = nn.Parameter(torch.randn(1, 1, config.hidden_size))
_Lin = select_bcos_unnormed_linear(config)
self.attention = AloeMultiHeadAttentionPooler(
embedding_dim=config.hidden_size,
num_heads=config.num_attention_heads,
out_b=config.aloe_b_linear,
out_proj_cls=_Lin,
attn_implementation=getattr(config, "_attn_implementation", "sdpa"),
attention_dropout=config.attention_dropout,
)
self.layernorm = NoBiasDetachableLayerNorm(config.hidden_size, eps=config.layer_norm_eps)
self.mlp = AloeMLP(config)
self.num_heads = config.num_attention_heads
self._use_flash_attention_2 = getattr(config, "_attn_implementation", None) == "flash_attention_2"
# Skip CLS token and register tokens — probe attends over patch tokens only.
self._skip_prefix = (1 if config.aloe_cls_token else 0) + config.aloe_num_registers
def forward(
self,
hidden_state: torch.Tensor,
attention_mask: Optional[torch.Tensor] = None,
output_attentions: bool = False,
) -> torch.Tensor:
"""Attention from a learned probe to patch tokens only (CLS+register prefix skipped), LN+MLP residual, first probe slot out."""
batch_size = hidden_state.shape[0]
probe = self.probe.expand(batch_size, -1, -1)
# Drop CLS and register tokens; probe only attends over patch tokens.
patch_state = hidden_state[:, self._skip_prefix:, :]
attn_mask = None
if attention_mask is not None:
patch_attention_mask = attention_mask[:, self._skip_prefix:]
if self._use_flash_attention_2 and not output_attentions:
attn_mask = patch_attention_mask
else:
attn_mask = _prepare_4d_attention_mask(
patch_attention_mask,
patch_state.dtype,
tgt_len=probe.shape[1],
)
hidden_state = self.attention(
probe, patch_state, patch_state, attn_mask=attn_mask, output_attentions=output_attentions
)[0]
residual = hidden_state
hidden_state = self.layernorm(hidden_state)
hidden_state = residual + self.mlp(hidden_state)
return hidden_state[:, 0]