EdoardoMosca commited on
Commit
293dacd
·
verified ·
1 Parent(s): d62e5a5

Add FA2-capable modeling file but disable flash_attention_2 via config (incompatible with PyLate query expansion; ~10 ndcg@10 loss)

Browse files
Files changed (3) hide show
  1. README.md +4 -0
  2. config.json +2 -1
  3. modeling_lfm2_bidirectional.py +37 -1
README.md CHANGED
@@ -67,3 +67,7 @@ reranked = rank.rerank(
67
  ```
68
 
69
  `trust_remote_code=True` is required: it loads `modeling_lfm2_bidirectional.py`, which replaces the causal attention mask and short-conv padding with their non-causal equivalents. Without it the model will silently use causal attention and produce poor embeddings.
 
 
 
 
 
67
  ```
68
 
69
  `trust_remote_code=True` is required: it loads `modeling_lfm2_bidirectional.py`, which replaces the causal attention mask and short-conv padding with their non-causal equivalents. Without it the model will silently use causal attention and produce poor embeddings.
70
+
71
+ ### Note on FlashAttention-2
72
+
73
+ `attn_implementation="flash_attention_2"` is intentionally rejected at load time (`disable_flash_attention` in `config.json`). ColBERT query expansion pads queries with mask tokens that have `attention_mask=0` but are still scored in MaxSim; FA2's unpadding cannot compute embeddings for those positions, which severely degrades retrieval quality (~10 ndcg@10 points on NanoBEIR). Use the default `sdpa` (or `eager`).
config.json CHANGED
@@ -60,5 +60,6 @@
60
  "vocab_size": 64402,
61
  "auto_map": {
62
  "AutoModel": "modeling_lfm2_bidirectional.Lfm2BidirectionalModel"
63
- }
 
64
  }
 
60
  "vocab_size": 64402,
61
  "auto_map": {
62
  "AutoModel": "modeling_lfm2_bidirectional.Lfm2BidirectionalModel"
63
+ },
64
+ "disable_flash_attention": true
65
  }
modeling_lfm2_bidirectional.py CHANGED
@@ -6,6 +6,17 @@ Wired into the HF repo via `auto_map` in config.json so that
6
  SentenceTransformer(repo, trust_remote_code=True)
7
 
8
  both return a model with the encoder-style patches already applied.
 
 
 
 
 
 
 
 
 
 
 
9
  """
10
 
11
  from typing import Optional
@@ -14,6 +25,7 @@ import torch
14
  import torch.nn.functional as F
15
  from transformers.models.lfm2 import modeling_lfm2 as _lfm2_mod
16
  from transformers.models.lfm2.modeling_lfm2 import (
 
17
  Lfm2Model,
18
  Lfm2ShortConv,
19
  apply_mask_to_padding_states,
@@ -27,7 +39,14 @@ def _bidirectional_mask(
27
  cache_position: Optional[torch.LongTensor],
28
  past_key_values,
29
  position_ids: Optional[torch.LongTensor],
30
- ) -> torch.Tensor:
 
 
 
 
 
 
 
31
  device = input_embeds.device
32
  dtype = input_embeds.dtype
33
  bsz, q_len = input_embeds.shape[:2]
@@ -52,6 +71,10 @@ def _noncausal_shortconv_forward(
52
  cache_position=None,
53
  attention_mask: Optional[torch.Tensor] = None,
54
  ) -> torch.Tensor:
 
 
 
 
55
  x = apply_mask_to_padding_states(hidden_states, attention_mask)
56
 
57
  BCx = self.in_proj(x).transpose(-1, -2)
@@ -98,5 +121,18 @@ class Lfm2BidirectionalModel(Lfm2Model):
98
  """LFM2 patched for encoder-style use: full bidirectional attention + non-causal short-conv."""
99
 
100
  def __init__(self, config):
 
 
 
 
 
 
 
 
 
 
101
  _install_patches()
102
  super().__init__(config)
 
 
 
 
6
  SentenceTransformer(repo, trust_remote_code=True)
7
 
8
  both return a model with the encoder-style patches already applied.
9
+
10
+ Supports `attn_implementation` in {"eager", "sdpa", "flash_attention_2"}:
11
+ eager/sdpa consume a 4D additive pad-only mask and reproduce the exact
12
+ training-time behavior; flash_attention_2 receives the 2D padding mask (or
13
+ None) and runs the kernel non-causally via `Lfm2Attention.is_causal = False`,
14
+ yielding outputs equivalent to the unpadded forward.
15
+
16
+ Repos may set `"disable_flash_attention": true` in config.json to reject
17
+ flash_attention_2 at load time (used for ColBERT, where PyLate query expansion
18
+ tokens — attention_mask=0 but scored in MaxSim — are incompatible with FA2
19
+ unpadding and severely degrade retrieval quality).
20
  """
21
 
22
  from typing import Optional
 
25
  import torch.nn.functional as F
26
  from transformers.models.lfm2 import modeling_lfm2 as _lfm2_mod
27
  from transformers.models.lfm2.modeling_lfm2 import (
28
+ Lfm2Attention,
29
  Lfm2Model,
30
  Lfm2ShortConv,
31
  apply_mask_to_padding_states,
 
39
  cache_position: Optional[torch.LongTensor],
40
  past_key_values,
41
  position_ids: Optional[torch.LongTensor],
42
+ ) -> Optional[torch.Tensor]:
43
+ if config._attn_implementation == "flash_attention_2":
44
+ # FA2 only uses the 2D padding mask to unpad sequences; causality is
45
+ # controlled by `Lfm2Attention.is_causal` (set to False below).
46
+ if attention_mask is not None and not attention_mask.all():
47
+ return attention_mask
48
+ return None
49
+
50
  device = input_embeds.device
51
  dtype = input_embeds.dtype
52
  bsz, q_len = input_embeds.shape[:2]
 
71
  cache_position=None,
72
  attention_mask: Optional[torch.Tensor] = None,
73
  ) -> torch.Tensor:
74
+ # eager/sdpa pass the 4D additive mask, on which this is a no-op — matching
75
+ # the behavior the checkpoints were trained with. FA2 passes the 2D pad
76
+ # mask, so pads are zeroed before the conv (closest match to the unpadded
77
+ # forward, since FA2 cannot reproduce the padded sdpa pad-state evolution).
78
  x = apply_mask_to_padding_states(hidden_states, attention_mask)
79
 
80
  BCx = self.in_proj(x).transpose(-1, -2)
 
121
  """LFM2 patched for encoder-style use: full bidirectional attention + non-causal short-conv."""
122
 
123
  def __init__(self, config):
124
+ if (
125
+ getattr(config, "_attn_implementation", None) == "flash_attention_2"
126
+ and getattr(config, "disable_flash_attention", False)
127
+ ):
128
+ raise ValueError(
129
+ "flash_attention_2 is disabled for this model: query expansion "
130
+ "tokens (attention_mask=0 but scored in MaxSim) are incompatible "
131
+ "with FA2 unpadding and severely degrade retrieval quality. "
132
+ "Load with attn_implementation='sdpa' (default) or 'eager'."
133
+ )
134
  _install_patches()
135
  super().__init__(config)
136
+ for module in self.modules():
137
+ if isinstance(module, Lfm2Attention):
138
+ module.is_causal = False