LFM2.5-Embedding-350M / modeling_lfm2_bidirectional.py
EdoardoMosca's picture
Initial release
bf1712f
Raw
History Blame Contribute Delete
5.19 kB
"""LFM2 backbone with bidirectional attention + non-causal short-conv, for retrieval/embedding use.
Wired into the HF repo via `auto_map` in config.json so that
AutoModel.from_pretrained(repo, trust_remote_code=True)
SentenceTransformer(repo, trust_remote_code=True)
both return a model with the encoder-style patches already applied.
Supports `attn_implementation` in {"eager", "sdpa", "flash_attention_2"}:
eager/sdpa consume a 4D additive pad-only mask and reproduce the exact
training-time behavior; flash_attention_2 receives the 2D padding mask (or
None) and runs the kernel non-causally via `Lfm2Attention.is_causal = False`,
yielding outputs equivalent to the unpadded forward.
Repos may set `"disable_flash_attention": true` in config.json to reject
flash_attention_2 at load time (used for ColBERT, where PyLate query expansion
tokens — attention_mask=0 but scored in MaxSim — are incompatible with FA2
unpadding and severely degrade retrieval quality).
"""
from typing import Optional
import torch
import torch.nn.functional as F
from transformers.models.lfm2 import modeling_lfm2 as _lfm2_mod
from transformers.models.lfm2.modeling_lfm2 import (
Lfm2Attention,
Lfm2Model,
Lfm2ShortConv,
apply_mask_to_padding_states,
)
def _bidirectional_mask(config, **kwargs) -> Optional[torch.Tensor]:
# transformers has renamed the embeds kwarg across versions
# (input_embeds <-> inputs_embeds); accept either to stay forward-compatible.
embeds = kwargs.get("inputs_embeds")
if embeds is None:
embeds = kwargs.get("input_embeds")
attention_mask = kwargs.get("attention_mask")
past_key_values = kwargs.get("past_key_values")
if config._attn_implementation == "flash_attention_2":
# FA2 only uses the 2D padding mask to unpad sequences; causality is
# controlled by `Lfm2Attention.is_causal` (set to False below).
if attention_mask is not None and not attention_mask.all():
return attention_mask
return None
device = embeds.device
dtype = embeds.dtype
bsz, q_len = embeds.shape[:2]
past = past_key_values.get_seq_length() if past_key_values is not None else 0
kv_len = past + q_len
mask = torch.zeros((bsz, 1, q_len, kv_len), device=device, dtype=dtype)
if attention_mask is not None:
cur_len = attention_mask.size(-1)
key_pad_flags = (attention_mask == 0).to(device=device, dtype=torch.float32)
pad_vec = torch.zeros((bsz, kv_len), device=device, dtype=torch.float32)
if cur_len > 0:
pad_vec[:, past:past + cur_len] = key_pad_flags * -1e9
mask = mask + pad_vec.to(dtype)[:, None, None, :]
return mask
def _noncausal_shortconv_forward(
self,
hidden_states: torch.Tensor,
past_key_values=None,
cache_position=None,
attention_mask: Optional[torch.Tensor] = None,
) -> torch.Tensor:
# eager/sdpa pass the 4D additive mask, on which this is a no-op — matching
# the behavior the checkpoints were trained with. FA2 passes the 2D pad
# mask, so pads are zeroed before the conv (closest match to the unpadded
# forward, since FA2 cannot reproduce the padded sdpa pad-state evolution).
x = apply_mask_to_padding_states(hidden_states, attention_mask)
BCx = self.in_proj(x).transpose(-1, -2)
B, C, x = BCx.chunk(3, dim=-2)
Bx = B * x
k = self.conv.weight.shape[-1]
pad = k // 2
conv_out = F.conv1d(
Bx, weight=self.conv.weight, bias=self.conv.bias,
stride=1, padding=pad, dilation=1, groups=Bx.shape[1],
)
if conv_out.shape[-1] > Bx.shape[-1]:
conv_out = conv_out[..., :Bx.shape[-1]]
elif conv_out.shape[-1] < Bx.shape[-1]:
conv_out = F.pad(conv_out, (0, Bx.shape[-1] - conv_out.shape[-1]))
y = C * conv_out
y = y.transpose(-1, -2).contiguous()
return self.out_proj(y)
def _shortconv_forward(self, *args, **kwargs):
return self.slow_forward(*args, **kwargs)
_PATCHED = False
def _install_patches() -> None:
global _PATCHED
if _PATCHED:
return
_lfm2_mod.create_causal_mask = _bidirectional_mask
Lfm2ShortConv.slow_forward = _noncausal_shortconv_forward
Lfm2ShortConv.forward = _shortconv_forward
_PATCHED = True
_install_patches()
class Lfm2BidirectionalModel(Lfm2Model):
"""LFM2 patched for encoder-style use: full bidirectional attention + non-causal short-conv."""
def __init__(self, config):
if (
getattr(config, "_attn_implementation", None) == "flash_attention_2"
and getattr(config, "disable_flash_attention", False)
):
raise ValueError(
"flash_attention_2 is disabled for this model: query expansion "
"tokens (attention_mask=0 but scored in MaxSim) are incompatible "
"with FA2 unpadding and severely degrade retrieval quality. "
"Load with attn_implementation='sdpa' (default) or 'eager'."
)
_install_patches()
super().__init__(config)
for module in self.modules():
if isinstance(module, Lfm2Attention):
module.is_causal = False