CoolFace
Modelpublic

Synthyra/ESM2-8M

sourceHugging Facemitupdated 2d agoView on Hugging Face
4likes581downloads
modeling_fastesm.py2584 linesDownload Raw Back to root
1from __future__ import annotations
2
3import torch
4import torch._inductor.config as inductor_config
5import torch._dynamo as dynamo
6
7# Enable TensorFloat32 tensor cores for float32 matmul (Ampere+ GPUs)
8# Provides significant speedup with minimal precision loss
9torch.set_float32_matmul_precision('high')
10
11# Enable TF32 for matrix multiplications and cuDNN operations
12torch.backends.cuda.matmul.allow_tf32 = True
13torch.backends.cudnn.allow_tf32 = True
14
15# Enable cuDNN autotuner - finds fastest algorithms for your hardware
16# Best when input sizes are consistent; may slow down first iterations
17torch.backends.cudnn.benchmark = True
18
19# Deterministic operations off for speed (set True if reproducibility needed)
20torch.backends.cudnn.deterministic = False
21inductor_config.max_autotune_gemm_backends = "ATEN,CUTLASS,FBGEMM"    
22
23dynamo.config.capture_scalar_outputs = True
24torch._dynamo.config.recompile_limit = 16
25
26import io
27import os
28import queue
29import sqlite3
30import struct
31import threading
32import time
33
34import networkx as nx
35import numpy as np
36import torch
37from tqdm.auto import tqdm
38from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple
39from torch.utils.data import DataLoader
40from torch.utils.data import Dataset as TorchDataset
41from transformers import PreTrainedTokenizerBase
42
43
44# SQLite stores tensors as compact blobs. Keep this header format compatible
45# with Protify readers that share the same dtype/version codes.
46_COMPACT_VERSION = 0x01
47_DTYPE_TO_CODE = {torch.float16: 0, torch.bfloat16: 1, torch.float32: 2}
48_CODE_TO_DTYPE = {0: torch.float16, 1: torch.bfloat16, 2: torch.float32}
49_CODE_TO_NP_DTYPE = {0: np.float16, 1: np.float16, 2: np.float32}
50
51
52def tensor_to_embedding_blob(tensor: torch.Tensor) -> bytes:
53    """Serialize a tensor to compact binary format for SQLite blob storage.
54
55    Format: [version:1][dtype_code:1][ndim:4][shape:4*ndim][raw_bytes]
56    bfloat16 tensors are stored as float16 bytes (numpy lacks bfloat16)
57    but tagged with dtype_code=1 so they can be cast back on read.
58    Falls back to torch.save for unsupported dtypes.
59    """
60    t = tensor.cpu()
61    if t.dtype not in _DTYPE_TO_CODE:
62        buffer = io.BytesIO()
63        torch.save(t, buffer)
64        return buffer.getvalue()
65    dtype_code = _DTYPE_TO_CODE[t.dtype]
66
67    if t.dtype == torch.bfloat16:
68        raw = t.half().numpy().tobytes()
69    else:
70        raw = t.numpy().tobytes()
71
72    shape = t.shape
73    header = struct.pack(f'<BBi{len(shape)}i', _COMPACT_VERSION, dtype_code, len(shape), *shape)
74    return header + raw
75
76
77def _compact_header(dtype: torch.dtype, shape: tuple) -> bytes:
78    """Build just the compact header for a given dtype and shape."""
79    dtype_code = _DTYPE_TO_CODE[dtype]
80    return struct.pack(f'<BBi{len(shape)}i', _COMPACT_VERSION, dtype_code, len(shape), *shape)
81
82
83def batch_tensor_to_blobs(batch: torch.Tensor) -> List[bytes]:
84    """Serialize a batch of same-shape tensors to compact blobs (fast path for vectors).
85
86    Builds the header once and slices raw bytes per row. Much faster than
87    per-row tensor_to_embedding_blob calls for uniform-shape batches.
88    """
89    assert batch.ndim >= 2, f"Expected batch with >= 2 dims, got {batch.ndim}"
90    t = batch.cpu()
91    store_dtype = t.dtype
92    if t.dtype not in _DTYPE_TO_CODE:
93        return [tensor_to_embedding_blob(t[i]) for i in range(t.shape[0])]
94
95    if t.dtype == torch.bfloat16:
96        arr = t.half().numpy()
97        store_dtype = torch.bfloat16
98    else:
99        arr = t.numpy()
100
101    row_shape = tuple(t.shape[1:])
102    header = _compact_header(store_dtype, row_shape)
103    raw = arr.tobytes()
104    stride = len(raw) // t.shape[0]
105    return [header + raw[i * stride:(i + 1) * stride] for i in range(t.shape[0])]
106
107
108def embedding_blob_to_tensor(blob: bytes, fallback_shape: Optional[Tuple[int, ...]] = None) -> torch.Tensor:
109    """Deserialize a blob back to a tensor. Auto-detects compact vs legacy formats."""
110    if len(blob) >= 6 and blob[0] == _COMPACT_VERSION:
111        dtype_code = blob[1]
112        ndim = struct.unpack_from('<i', blob, 2)[0]
113        shape = struct.unpack_from(f'<{ndim}i', blob, 6)
114        data_offset = 6 + 4 * ndim
115        np_dtype = _CODE_TO_NP_DTYPE[dtype_code]
116        arr = np.frombuffer(blob, dtype=np_dtype, offset=data_offset).copy().reshape(shape)
117        t = torch.from_numpy(arr)
118        target_dtype = _CODE_TO_DTYPE[dtype_code]
119        if target_dtype != t.dtype:
120            t = t.to(target_dtype)
121        return t
122
123    # Older `.pth`-style blobs were written with torch.save.
124    try:
125        buffer = io.BytesIO(blob)
126        return torch.load(buffer, map_location='cpu', weights_only=True)
127    except Exception:
128        pass
129
130    # Oldest SQLite rows stored raw float32 bytes and need a caller-supplied shape.
131    assert fallback_shape is not None, "Cannot deserialize blob: unknown format and no fallback_shape provided."
132    arr = np.frombuffer(blob, dtype=np.float32).copy().reshape(fallback_shape)
133    return torch.from_numpy(arr)
134
135
136def select_hidden_state_embeddings(
137    last_hidden_state: torch.Tensor,
138    hidden_states: Optional[Tuple[torch.Tensor, ...]],
139    hidden_state_index: int = -1,
140    store_all_hidden_states: bool = False,
141) -> torch.Tensor:
142    assert isinstance(hidden_state_index, int), "hidden_state_index must be an integer."
143    if store_all_hidden_states:
144        assert hidden_states is not None, "store_all_hidden_states requires output_hidden_states=True."
145        assert len(hidden_states) > 0, "Model returned no hidden states."
146        return torch.stack(tuple(hidden_states), dim=1)
147    if hidden_state_index == -1:
148        return last_hidden_state
149    assert hidden_states is not None, "hidden_state_index selection requires output_hidden_states=True."
150    return hidden_states[hidden_state_index]
151
152
153def _trim_full_embedding(embedding: torch.Tensor, attention_mask: torch.Tensor) -> torch.Tensor:
154    mask = attention_mask.bool()
155    if embedding.ndim == 2:
156        return embedding[mask].reshape(-1, embedding.shape[-1])
157    if embedding.ndim == 3:
158        return embedding[:, mask, :].reshape(embedding.shape[0], -1, embedding.shape[-1])
159    raise AssertionError(f"Expected full embedding tensor with 2 or 3 dims, got {embedding.ndim}.")
160
161
162def pool_embeddings(
163    embeddings: Dict[str, torch.Tensor],
164    pooling_types: List[str] = ['mean'],
165    hidden_state_index: int = -1,
166) -> Dict[str, torch.Tensor]:
167    pooler = Pooler(pooling_types)
168    pooled: Dict[str, torch.Tensor] = {}
169    for sequence, embedding in embeddings.items():
170        assert isinstance(sequence, str), "Expected embedding dictionary keys to be sequences (str)."
171        assert isinstance(embedding, torch.Tensor), "Expected embedding dictionary values to be tensors."
172        if embedding.ndim == 1:
173            pooled[sequence] = embedding.cpu()
174            continue
175        if embedding.ndim == 3:
176            embedding = embedding[hidden_state_index]
177        assert embedding.ndim == 2, f"Expected token-wise embedding with 2 dims, got {embedding.ndim}."
178        pooled[sequence] = pooler(embedding.unsqueeze(0)).squeeze(0).cpu()
179    return pooled
180
181
182def load_pooled_embeddings_from_pth(
183    save_path: str,
184    pooling_types: List[str] = ['mean'],
185    hidden_state_index: int = -1,
186) -> Dict[str, torch.Tensor]:
187    assert os.path.exists(save_path), f"Embedding file does not exist: {save_path}"
188    payload = torch.load(save_path, map_location="cpu", weights_only=True)
189    assert isinstance(payload, dict), "Expected .pth embeddings file to contain a dictionary."
190    return pool_embeddings(payload, pooling_types=pooling_types, hidden_state_index=hidden_state_index)
191
192
193def load_pooled_embeddings_from_db(
194    db_path: str,
195    sequences: Optional[List[str]] = None,
196    pooling_types: List[str] = ['mean'],
197    hidden_state_index: int = -1,
198) -> Dict[str, torch.Tensor]:
199    assert os.path.exists(db_path), f"Embedding database does not exist: {db_path}"
200    loaded: Dict[str, torch.Tensor] = {}
201    with sqlite3.connect(db_path, timeout=30) as conn:
202        cursor = conn.cursor()
203        if sequences is None:
204            cursor.execute("SELECT sequence, embedding FROM embeddings")
205        else:
206            if len(sequences) == 0:
207                return loaded
208            placeholders = ",".join(["?"] * len(sequences))
209            cursor.execute(
210                f"SELECT sequence, embedding FROM embeddings WHERE sequence IN ({placeholders})",
211                tuple(sequences),
212            )
213        for sequence, embedding_bytes in cursor.fetchall():
214            loaded[sequence] = embedding_blob_to_tensor(embedding_bytes)
215    return pool_embeddings(loaded, pooling_types=pooling_types, hidden_state_index=hidden_state_index)
216
217
218def maybe_compile(model: torch.nn.Module, dynamic: bool = False) -> torch.nn.Module:
219    """Compile model with torch.compile if possible.
220
221    Skips compilation when dynamic=True (padding='longest') because
222    flex attention's create_block_mask is incompatible with dynamic shapes
223    under torch.compile, causing CUDA illegal memory access.
224    """
225    if dynamic:
226        print("Skipping torch.compile (dynamic shapes + flex attention incompatible)")
227        return model
228    try:
229        model = torch.compile(model)
230        print("Model compiled")
231    except Exception as e:
232        print(f"Skipping torch.compile: {e}")
233    return model
234
235
236def build_collator(
237    tokenizer: PreTrainedTokenizerBase,
238    padding: str = 'max_length',
239    max_length: int = 512,
240) -> Callable[[List[str]], Dict[str, torch.Tensor]]:
241    def _collate_fn(sequences: List[str]) -> Dict[str, torch.Tensor]:
242        kwargs: Dict[str, Any] = dict(
243            return_tensors="pt", padding=padding, truncation=True, max_length=max_length,
244        )
245        if padding != 'max_length':
246            kwargs['pad_to_multiple_of'] = 8
247        return tokenizer(sequences, **kwargs)
248    return _collate_fn
249
250
251def _make_embedding_progress(
252    dataloader: DataLoader,
253    padding: str,
254    n_warmup: int = 3,
255    n_calibration: int = 5,
256) -> Iterator[Tuple[int, Any]]:
257    """Progress-bar wrapper for embedding loops. Drop-in replacement for enumerate(dataloader).
258
259    When padding='max_length', all batches have uniform cost so plain tqdm works.
260    When padding='longest' (sorted longest-first), batch times vary dramatically.
261    In that case: yield warmup batches first (compiler warmup + OOM check on longest
262    sequences), then time mid-length calibration batches to estimate total ETA.
263
264    Keep in sync with protify/embedder.py and core/atlas/precomputed.py.
265    """
266    total = len(dataloader)
267    if padding == 'max_length' or total <= n_warmup + n_calibration:
268        for i, batch in tqdm(enumerate(dataloader), total=total, desc='Embedding batches'):
269            yield i, batch
270        return
271
272    dl_iter = iter(dataloader)
273
274    # Warm up on the longest batches first; sorted inputs make these the OOM-risk
275    # and compile-stabilization cases.
276    warmup_bar = tqdm(range(n_warmup), desc='Warmup (longest batches)', leave=False)
277    for i in warmup_bar:
278        batch = next(dl_iter)
279        yield i, batch
280    warmup_bar.close()
281
282    # Move toward mid-length batches for ETA calibration, yielding every real
283    # batch on the way so no sequences are skipped.
284    mid_start = total // 2
285    intermediate_bar = tqdm(
286        range(n_warmup, mid_start), desc='Embedding batches', leave=False,
287    )
288    for i in intermediate_bar:
289        batch = next(dl_iter)
290        yield i, batch
291    intermediate_bar.close()
292
293    # Mid-length batches give a better remaining-time estimate than the longest
294    # warmup batches.
295    calibration_times: List[float] = []
296    cal_bar = tqdm(range(n_calibration), desc='Calibrating ETA', leave=False)
297    for j in cal_bar:
298        t0 = time.perf_counter()
299        batch = next(dl_iter)
300        yield mid_start + j, batch
301        calibration_times.append(time.perf_counter() - t0)
302    cal_bar.close()
303
304    avg_time = sum(calibration_times) / len(calibration_times)
305    remaining_start = mid_start + n_calibration
306    remaining_count = total - remaining_start
307    estimated_total_seconds = avg_time * remaining_count
308
309    # Finish the tail with the calibrated ETA shown in the progress bar.
310    main_bar = tqdm(
311        range(remaining_count),
312        desc='Embedding batches',
313        bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]',
314    )
315    main_bar.set_postfix_str(f'ETA ~{estimated_total_seconds:.0f}s (calibrated)')
316    for k in main_bar:
317        batch = next(dl_iter)
318        yield remaining_start + k, batch
319    main_bar.close()
320
321
322class _SQLWriter:
323    """Context manager for async SQL embedding writes. Matches core/embed/storage.SQLEmbeddingWriter."""
324
325    def __init__(self, conn: sqlite3.Connection, queue_maxsize: int = 4) -> None:
326        self._conn = conn
327        self._queue: queue.Queue = queue.Queue(maxsize=queue_maxsize)
328        self._thread: Optional[threading.Thread] = None
329
330    def __enter__(self) -> "_SQLWriter":
331        self._thread = threading.Thread(target=self._writer_loop, daemon=True)
332        self._thread.start()
333        return self
334
335    def write_batch(self, rows: List[Tuple[str, bytes]]) -> None:
336        self._queue.put(rows)
337
338    def _writer_loop(self) -> None:
339        cursor = self._conn.cursor()
340        while True:
341            item = self._queue.get()
342            if item is None:
343                break
344            cursor.executemany("INSERT OR REPLACE INTO embeddings VALUES (?, ?)", item)
345            if self._queue.qsize() == 0:
346                self._conn.commit()
347        self._conn.commit()
348
349    def __exit__(self, *exc) -> None:
350        if self._thread is not None:
351            self._queue.put(None)
352            self._thread.join()
353            self._thread = None
354
355
356class Pooler:
357    def __init__(self, pooling_types: List[str]) -> None:
358        self.pooling_types = pooling_types
359        self.pooling_options: Dict[str, Callable] = {
360            'mean': self.mean_pooling,
361            'max': self.max_pooling,
362            'norm': self.norm_pooling,
363            'median': self.median_pooling,
364            'std': self.std_pooling,
365            'var': self.var_pooling,
366            'cls': self.cls_pooling,
367            'parti': self._pool_parti,
368        }
369
370    def _create_pooled_matrices_across_layers(self, attentions: torch.Tensor) -> torch.Tensor:
371        assert isinstance(attentions, torch.Tensor)
372        maxed_attentions = torch.max(attentions, dim=1)[0]
373        return maxed_attentions
374
375    def _page_rank(self, attention_matrix: np.ndarray, personalization: Optional[dict] = None, nstart: Optional[dict] = None, prune_type: str = "top_k_outdegree") -> Dict[int, float]:
376        G = self._convert_to_graph(attention_matrix)
377        if G.number_of_nodes() != attention_matrix.shape[0]:
378            raise Exception(
379                f"The number of nodes in the graph should be equal to the number of tokens in sequence! You have {G.number_of_nodes()} nodes for {attention_matrix.shape[0]} tokens.")
380        if G.number_of_edges() == 0:
381            raise Exception(f"You don't seem to have any attention edges left in the graph.")
382
383        return nx.pagerank(G, alpha=0.85, tol=1e-06, weight='weight', personalization=personalization, nstart=nstart, max_iter=100)
384
385    def _convert_to_graph(self, matrix: np.ndarray) -> nx.DiGraph:
386        G = nx.from_numpy_array(matrix, create_using=nx.DiGraph)
387        return G
388
389    def _calculate_importance_weights(self, dict_importance: Dict[int, float], attention_mask: Optional[torch.Tensor] = None) -> np.ndarray:
390        if attention_mask is not None:
391            for k in list(dict_importance.keys()):
392                if attention_mask[k] == 0:
393                    del dict_importance[k]
394
395        total = sum(dict_importance.values())
396        return np.array([v / total for _, v in dict_importance.items()])
397
398    def _pool_parti(self, emb: torch.Tensor, attentions: torch.Tensor, attention_mask: Optional[torch.Tensor] = None) -> torch.Tensor:
399        maxed_attentions = self._create_pooled_matrices_across_layers(attentions).numpy()
400        emb_pooled = []
401        for e, a, mask in zip(emb, maxed_attentions, attention_mask):
402            dict_importance = self._page_rank(a)
403            importance_weights = self._calculate_importance_weights(dict_importance, mask)
404            num_tokens = int(mask.sum().item())
405            emb_pooled.append(np.average(e[:num_tokens], weights=importance_weights, axis=0))
406        pooled = torch.tensor(np.array(emb_pooled))
407        return pooled
408
409    def mean_pooling(self, emb: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, **kwargs) -> torch.Tensor:
410        if attention_mask is None:
411            return emb.mean(dim=1)
412        else:
413            attention_mask = attention_mask.unsqueeze(-1)
414            return (emb * attention_mask).sum(dim=1) / attention_mask.sum(dim=1)
415
416    def max_pooling(self, emb: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, **kwargs) -> torch.Tensor:
417        if attention_mask is None:
418            return emb.max(dim=1).values
419        else:
420            mask = attention_mask.unsqueeze(-1).bool()
421            return emb.masked_fill(~mask, float('-inf')).max(dim=1).values
422
423    def norm_pooling(self, emb: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, **kwargs) -> torch.Tensor:
424        if attention_mask is None:
425            return emb.norm(dim=1, p=2)
426        else:
427            attention_mask = attention_mask.unsqueeze(-1)
428            return (emb * attention_mask).norm(dim=1, p=2)
429
430    def median_pooling(self, emb: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, **kwargs) -> torch.Tensor:
431        if attention_mask is None:
432            return emb.median(dim=1).values
433        else:
434            mask = attention_mask.unsqueeze(-1).bool()
435            return emb.masked_fill(~mask, float('nan')).nanmedian(dim=1).values
436
437    def std_pooling(self, emb: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, **kwargs) -> torch.Tensor:
438        if attention_mask is None:
439            return emb.std(dim=1)
440        else:
441            var = self.var_pooling(emb, attention_mask, **kwargs)
442            return torch.sqrt(var)
443
444    def var_pooling(self, emb: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, **kwargs) -> torch.Tensor:
445        if attention_mask is None:
446            return emb.var(dim=1)
447        else:
448            attention_mask = attention_mask.unsqueeze(-1)
449            mean = (emb * attention_mask).sum(dim=1) / attention_mask.sum(dim=1)
450            mean = mean.unsqueeze(1)
451            squared_diff = (emb - mean) ** 2
452            var = (squared_diff * attention_mask).sum(dim=1) / attention_mask.sum(dim=1)
453            return var
454
455    def cls_pooling(self, emb: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, **kwargs) -> torch.Tensor:
456        return emb[:, 0, :]
457
458    def __call__(
459            self,
460            emb: torch.Tensor,
461            attention_mask: Optional[torch.Tensor] = None,
462            attentions: Optional[torch.Tensor] = None
463        ) -> torch.Tensor:
464        if attention_mask is not None:
465            assert attention_mask.sum(dim=-1).min() > 0, (
466                "Pooler received samples with all-zero attention masks. "
467                "This causes NaN from division by zero. Filter empty inputs before pooling."
468            )
469        final_emb: List[torch.Tensor] = []
470        for pooling_type in self.pooling_types:
471            final_emb.append(self.pooling_options[pooling_type](emb=emb, attention_mask=attention_mask, attentions=attentions))
472        return torch.cat(final_emb, dim=-1)
473
474
475class ProteinDataset(TorchDataset):
476    """Simple dataset for protein sequences."""
477    def __init__(self, sequences: List[str]) -> None:
478        self.sequences = sequences
479
480    def __len__(self) -> int:
481        return len(self.sequences)
482
483    def __getitem__(self, idx: int) -> str:
484        return self.sequences[idx]
485
486
487def parse_fasta(fasta_path: str) -> List[str]:
488    assert os.path.exists(fasta_path), f"FASTA file does not exist: {fasta_path}"
489    sequences = []
490    current_seq = []
491    with open(fasta_path, 'r') as f:
492        for line in f:
493            line = line.strip()
494            if not line:
495                continue
496            if line.startswith('>'):
497                if current_seq:
498                    sequences.append(''.join(current_seq))
499                    current_seq = []
500            else:
501                current_seq.append(line)
502    if current_seq:
503        sequences.append(''.join(current_seq))
504    return sequences
505
506
507class EmbeddingMixin:
508    def _embed(
509        self,
510        input_ids: torch.Tensor,
511        attention_mask: Optional[torch.Tensor] = None,
512        hidden_state_index: int = -1,
513        store_all_hidden_states: bool = False,
514    ) -> torch.Tensor:
515        raise NotImplementedError
516
517    @property
518    def device(self) -> torch.device:
519        """Get the device of the model."""
520        return next(self.parameters()).device
521
522    def _read_sequences_from_db(self, db_path: str) -> Set[str]:
523        """Read sequences from SQLite database."""
524        with sqlite3.connect(db_path, timeout=30) as conn:
525            c = conn.cursor()
526            c.execute("SELECT sequence FROM embeddings")
527            return {row[0] for row in c.fetchall()}
528
529    def _ensure_embeddings_table(self, conn: sqlite3.Connection) -> None:
530        cursor = conn.cursor()
531        cursor.execute(
532            "CREATE TABLE IF NOT EXISTS embeddings ("
533            "sequence TEXT PRIMARY KEY, "
534            "embedding BLOB NOT NULL"
535            ")"
536        )
537        conn.commit()
538
539    def load_embeddings_from_pth(self, save_path: str) -> Dict[str, torch.Tensor]:
540        assert os.path.exists(save_path), f"Embedding file does not exist: {save_path}"
541        payload = torch.load(save_path, map_location="cpu", weights_only=True)
542        assert isinstance(payload, dict), "Expected .pth embeddings file to contain a dictionary."
543        for sequence, tensor in payload.items():
544            assert isinstance(sequence, str), "Expected embedding dictionary keys to be sequences (str)."
545            assert isinstance(tensor, torch.Tensor), "Expected embedding dictionary values to be tensors."
546        return payload
547
548    def load_embeddings_from_db(self, db_path: str, sequences: Optional[List[str]] = None) -> Dict[str, torch.Tensor]:
549        assert os.path.exists(db_path), f"Embedding database does not exist: {db_path}"
550        loaded: Dict[str, torch.Tensor] = {}
551        with sqlite3.connect(db_path, timeout=30) as conn:
552            self._ensure_embeddings_table(conn)
553            cursor = conn.cursor()
554            if sequences is None:
555                cursor.execute("SELECT sequence, embedding FROM embeddings")
556            else:
557                if len(sequences) == 0:
558                    return loaded
559                placeholders = ",".join(["?"] * len(sequences))
560                cursor.execute(
561                    f"SELECT sequence, embedding FROM embeddings WHERE sequence IN ({placeholders})",
562                    tuple(sequences),
563                )
564
565            rows = cursor.fetchall()
566            for row in rows:
567                sequence = row[0]
568                embedding_bytes = row[1]
569                loaded[sequence] = embedding_blob_to_tensor(embedding_bytes)
570        return loaded
571
572    def pool_embeddings(
573        self,
574        embeddings: Dict[str, torch.Tensor],
575        pooling_types: List[str] = ['mean'],
576        hidden_state_index: int = -1,
577    ) -> Dict[str, torch.Tensor]:
578        return pool_embeddings(embeddings, pooling_types=pooling_types, hidden_state_index=hidden_state_index)
579
580    def load_pooled_embeddings_from_pth(
581        self,
582        save_path: str,
583        pooling_types: List[str] = ['mean'],
584        hidden_state_index: int = -1,
585    ) -> Dict[str, torch.Tensor]:
586        return load_pooled_embeddings_from_pth(
587            save_path,
588            pooling_types=pooling_types,
589            hidden_state_index=hidden_state_index,
590        )
591
592    def load_pooled_embeddings_from_db(
593        self,
594        db_path: str,
595        sequences: Optional[List[str]] = None,
596        pooling_types: List[str] = ['mean'],
597        hidden_state_index: int = -1,
598    ) -> Dict[str, torch.Tensor]:
599        return load_pooled_embeddings_from_db(
600            db_path,
601            sequences=sequences,
602            pooling_types=pooling_types,
603            hidden_state_index=hidden_state_index,
604        )
605
606    def embed_dataset(
607        self,
608        sequences: Optional[List[str]] = None,
609        tokenizer: Optional[PreTrainedTokenizerBase] = None,
610        batch_size: int = 2,
611        max_len: int = 512,
612        truncate: bool = True,
613        full_embeddings: bool = False,
614        embed_dtype: torch.dtype = torch.float32,
615        pooling_types: List[str] = ['mean'],
616        num_workers: int = 0,
617        sql: bool = False,
618        save: bool = True,
619        sql_db_path: str = 'embeddings.db',
620        save_path: str = 'embeddings.pth',
621        fasta_path: Optional[str] = None,
622        padding: str = 'max_length',
623        hidden_state_index: int = -1,
624        store_all_hidden_states: bool = False,
625        **kwargs,
626    ) -> Optional[Dict[str, torch.Tensor]]:
627        """
628        Embed a dataset of protein sequences.
629
630        Supports two modes:
631        - Tokenizer mode (ESM2/ESM++): provide `tokenizer` or use `self.tokenizer`.
632        - Sequence mode (E1): pass `tokenizer=None`, `_embed(sequences, return_attention_mask=True, **kwargs)` is used.
633
634        Sequences can be supplied as a list via `sequences`, parsed from a FASTA file via
635        `fasta_path`, or both (the two sources are combined). At least one must be provided.
636        """
637        if fasta_path is not None:
638            fasta_sequences = parse_fasta(fasta_path)
639            sequences = list(sequences or []) + fasta_sequences
640        assert sequences is not None and len(sequences) > 0, \
641            "Must provide at least one sequence via `sequences` or `fasta_path`."
642        assert isinstance(hidden_state_index, int), "hidden_state_index must be an integer."
643        assert full_embeddings or not store_all_hidden_states, \
644            "store_all_hidden_states=True requires full_embeddings=True."
645        sequences = list(set([seq[:max_len] if truncate else seq for seq in sequences]))
646        sequences = sorted(sequences, key=len, reverse=True)
647        pooler = Pooler(pooling_types) if not full_embeddings else None
648        if tokenizer is None and self.config.model_type != "E1":
649            tokenizer = self.tokenizer
650        tokenizer_mode = tokenizer is not None
651
652        # Resolve padding and compilation
653        dynamic = padding == 'longest'
654        compiled_model = maybe_compile(self, dynamic=dynamic)
655
656        if tokenizer_mode:
657            collate_fn = build_collator(tokenizer, padding=padding, max_length=max_len)
658            device = self.device
659        else:
660            collate_fn = None
661            device = None
662
663        def get_embeddings(residue_embeddings: torch.Tensor, attention_mask: Optional[torch.Tensor] = None) -> torch.Tensor:
664            assert isinstance(residue_embeddings, torch.Tensor)
665            if full_embeddings or residue_embeddings.ndim == 2:
666                return residue_embeddings
667            return pooler(residue_embeddings, attention_mask)
668
669        def iter_batches(to_embed: List[str]):
670            if tokenizer_mode:
671                assert collate_fn is not None
672                assert device is not None
673                dataset = ProteinDataset(to_embed)
674                dataloader = DataLoader(
675                    dataset,
676                    batch_size=batch_size,
677                    num_workers=num_workers,
678                    prefetch_factor=2 if num_workers > 0 else None,
679                    collate_fn=collate_fn,
680                    shuffle=False,
681                    pin_memory=True,
682                )
683                for i, batch in _make_embedding_progress(dataloader, padding):
684                    seqs = to_embed[i * batch_size:(i + 1) * batch_size]
685                    input_ids = batch['input_ids'].to(device)
686                    attention_mask = batch['attention_mask'].to(device)
687                    residue_embeddings = compiled_model._embed(
688                        input_ids,
689                        attention_mask,
690                        hidden_state_index=hidden_state_index,
691                        store_all_hidden_states=store_all_hidden_states,
692                    )
693                    yield seqs, residue_embeddings, attention_mask
694            else:
695                for batch_start in tqdm(range(0, len(to_embed), batch_size), desc='Embedding batches'):
696                    seqs = to_embed[batch_start:batch_start + batch_size]
697                    batch_output = compiled_model._embed(
698                        seqs,
699                        return_attention_mask=True,
700                        hidden_state_index=hidden_state_index,
701                        store_all_hidden_states=store_all_hidden_states,
702                        **kwargs,
703                    )
704                    assert isinstance(batch_output, tuple), "Sequence mode _embed must return (last_hidden_state, attention_mask)."
705                    assert len(batch_output) == 2, "Sequence mode _embed must return exactly two values."
706                    residue_embeddings, attention_mask = batch_output
707                    assert isinstance(attention_mask, torch.Tensor), "Sequence mode _embed must return attention_mask as a torch.Tensor."
708                    yield seqs, residue_embeddings, attention_mask
709
710        if sql:
711            # Resume safely: skip sequences already present in the SQLite table.
712            conn = sqlite3.connect(sql_db_path, timeout=30, check_same_thread=False)
713            conn.execute('PRAGMA journal_mode=WAL')
714            conn.execute('PRAGMA busy_timeout=30000')
715            conn.execute('PRAGMA synchronous=OFF')
716            conn.execute('PRAGMA cache_size=-64000')
717            self._ensure_embeddings_table(conn)
718            already_embedded = self._read_sequences_from_db(sql_db_path)
719            to_embed = [seq for seq in sequences if seq not in already_embedded]
720            print(f"Found {len(already_embedded)} already embedded sequences in {sql_db_path}")
721            print(f"Embedding {len(to_embed)} new sequences")
722            if len(to_embed) > 0:
723                # Embed batches synchronously; serialize/write them on the SQL writer thread.
724                with _SQLWriter(conn) as writer:
725                    with torch.inference_mode():
726                        for seqs, residue_embeddings, attention_mask in iter_batches(to_embed):
727                            embeddings = get_embeddings(residue_embeddings, attention_mask).to(embed_dtype)
728                            if full_embeddings:
729                                batch_rows = []
730                                for seq, emb, mask in zip(seqs, embeddings, attention_mask):
731                                    batch_rows.append((seq, tensor_to_embedding_blob(_trim_full_embedding(emb, mask))))
732                            else:
733                                blobs = batch_tensor_to_blobs(embeddings)
734                                batch_rows = list(zip(seqs, blobs))
735                            writer.write_batch(batch_rows)
736            conn.close()
737            return None
738
739        embeddings_dict = {}
740        if os.path.exists(save_path):
741            embeddings_dict = self.load_embeddings_from_pth(save_path)
742            to_embed = [seq for seq in sequences if seq not in embeddings_dict]
743            print(f"Found {len(embeddings_dict)} already embedded sequences in {save_path}")
744            print(f"Embedding {len(to_embed)} new sequences")
745        else:
746            to_embed = sequences
747            print(f"Embedding {len(to_embed)} new sequences")
748
749        if len(to_embed) > 0:
750            with torch.inference_mode():
751                for seqs, residue_embeddings, attention_mask in iter_batches(to_embed):
752                    embeddings = get_embeddings(residue_embeddings, attention_mask).to(embed_dtype)
753                    for seq, emb, mask in zip(seqs, embeddings, attention_mask):
754                        if full_embeddings:
755                            emb = _trim_full_embedding(emb, mask)
756                        embeddings_dict[seq] = emb.cpu()
757
758        if save:
759            torch.save(embeddings_dict, save_path)
760
761        return embeddings_dict
762
763
764if __name__ == "__main__":
765    # Manual smoke test for pooling shape behavior.
766    pooler = Pooler(pooling_types=['max', 'parti'])
767    batch_size = 8
768    seq_len = 64
769    hidden_size = 128
770    num_layers = 12
771    emb = torch.randn(batch_size, seq_len, hidden_size)
772    attentions = torch.randn(batch_size, num_layers, seq_len, seq_len)
773    attention_mask = torch.ones(batch_size, seq_len)
774    y = pooler(emb=emb, attention_mask=attention_mask, attentions=attentions)
775    print(y.shape)
776
777"""Shared attention infrastructure for all FastPLMs models.
778
779Contains: AttentionBackend enum, backend resolution, mask creation,
780flex attention helpers, flash kernel detection/dispatch, and pad/unpad utilities.
781"""
782from enum import Enum
783from typing import Dict, List, Optional, Tuple
784
785import torch
786import torch.nn as nn
787from torch.nn import functional as F
788from einops import rearrange
789
790try:
791    from torch.nn.attention.flex_attention import create_block_mask, flex_attention, BlockMask
792except ImportError:
793    create_block_mask = None
794    flex_attention = None
795    BlockMask = None
796
797_compiled_flex_attention = None
798
799
800def _get_flex_attention_fn():
801    """Return flex_attention callable: compiled (fused kernel) by default, or eager when debug flag is set."""
802    global _compiled_flex_attention
803    if flex_attention is None:
804        return None
805    flex_mod = torch.nn.attention.flex_attention
806    if getattr(flex_mod, "_FLEX_ATTENTION_DISABLE_COMPILE_DEBUG", False):
807        return flex_attention
808    if _compiled_flex_attention is None:
809        _compiled_flex_attention = torch.compile(
810            flex_attention,
811            dynamic=False,
812        )
813    return _compiled_flex_attention
814
815
816# HuggingFace `kernels` exposes slightly different APIs for Flash Attention 2
817# and 3. Detect the loaded variant once so every caller uses the same dispatch.
818def _infer_kernels_flash_variant(kernel) -> Optional[str]:
819    if hasattr(kernel, "fwd") and hasattr(kernel, "varlen_fwd"):
820        return "flash_attn2"
821    if hasattr(kernel, "flash_attn_func") and hasattr(kernel, "flash_attn_varlen_func"):
822        return "flash_attn3"
823    return None
824
825
826def _try_get_kernels_flash():
827    try:
828        from kernels import get_kernel
829    except ImportError:
830        return None, None
831
832    flash_kernel = None
833    flash_kernel_variant = None
834    try:
835        flash_kernel = get_kernel("kernels-community/flash-attn3")
836        flash_kernel_variant = _infer_kernels_flash_variant(flash_kernel)
837        assert flash_kernel_variant is not None, "Loaded flash-attn3 kernel does not expose a supported API."
838    except Exception:
839        try:
840            flash_kernel = get_kernel("kernels-community/flash-attn2")
841            flash_kernel_variant = _infer_kernels_flash_variant(flash_kernel)
842            assert flash_kernel_variant is not None, "Loaded flash-attn2 kernel does not expose a supported API."
843        except Exception:
844            flash_kernel = None
845            flash_kernel_variant = None
846    return flash_kernel, flash_kernel_variant
847
848
849_FLASH_KERNELS_LOADED = False
850FLASH_KERNEL = None
851FLASH_KERNEL_VARIANT = None
852
853
854def _ensure_flash_kernels_loaded():
855    global _FLASH_KERNELS_LOADED, FLASH_KERNEL, FLASH_KERNEL_VARIANT
856    if _FLASH_KERNELS_LOADED:
857        return
858    _FLASH_KERNELS_LOADED = True
859    FLASH_KERNEL, FLASH_KERNEL_VARIANT = _try_get_kernels_flash()
860
861
862def _kernels_flash_forward(
863    query_states: torch.Tensor,
864    key_states: torch.Tensor,
865    value_states: torch.Tensor,
866    causal: bool = False,
867    softmax_scale: Optional[float] = None,
868) -> torch.Tensor:
869    """Flash-attention forward, optionally overriding the softmax scale.
870
871    When `softmax_scale is None`, the flash kernel applies its default
872    `1 / sqrt(head_dim)`. Pass `softmax_scale=1.0` if the caller has already
873    pre-scaled Q (the convention used by ESM2, DPLM, DPLM2, E1, ESMFold).
874    Failing to override when Q is pre-scaled applies the scale twice. On
875    DPLM-150M, that produced pooled-embedding cosine around -0.12 and argmax
876    agreement around 0.27 vs SDPA.
877    """
878    assert FLASH_KERNEL is not None, "Kernel Flash Attention is not available in this environment."
879    if FLASH_KERNEL_VARIANT == "flash_attn2":
880        return FLASH_KERNEL.fwd(
881            q=query_states, k=key_states, v=value_states,
882            softmax_scale=softmax_scale, is_causal=causal,
883        )[0]
884    if FLASH_KERNEL_VARIANT == "flash_attn3":
885        try:
886            output = FLASH_KERNEL.flash_attn_func(
887                q=query_states, k=key_states, v=value_states,
888                softmax_scale=softmax_scale, causal=causal,
889            )
890        except TypeError:
891            output = FLASH_KERNEL.flash_attn_func(
892                query_states, key_states, value_states,
893                0.0, softmax_scale, causal,
894            )
895        if isinstance(output, tuple):
896            return output[0]
897        return output
898    raise AssertionError(f"Unsupported kernels flash attention variant: {FLASH_KERNEL_VARIANT}")
899
900
901def _kernels_flash_varlen_forward(
902    query_states: torch.Tensor,
903    key_states: torch.Tensor,
904    value_states: torch.Tensor,
905    cu_seqlens_q: torch.Tensor,
906    cu_seqlens_k: torch.Tensor,
907    max_seqlen_in_batch_q: int,
908    max_seqlen_in_batch_k: int,
909    causal: bool = False,
910    softmax_scale: Optional[float] = None,
911) -> torch.Tensor:
912    """Varlen flash-attention forward, optionally overriding the softmax scale.
913
914    See `_kernels_flash_forward` docstring for why `softmax_scale=1.0` must be
915    passed when Q has been pre-scaled by the caller.
916    """
917    assert FLASH_KERNEL is not None, "Kernel Flash Attention is not available in this environment."
918    if FLASH_KERNEL_VARIANT == "flash_attn2":
919        return FLASH_KERNEL.varlen_fwd(
920            q=query_states, k=key_states, v=value_states,
921            cu_seqlens_q=cu_seqlens_q, cu_seqlens_k=cu_seqlens_k,
922            max_seqlen_q=max_seqlen_in_batch_q, max_seqlen_k=max_seqlen_in_batch_k,
923            softmax_scale=softmax_scale, is_causal=causal,
924        )[0]
925    if FLASH_KERNEL_VARIANT == "flash_attn3":
926        try:
927            output = FLASH_KERNEL.flash_attn_varlen_func(
928                q=query_states, k=key_states, v=value_states,
929                cu_seqlens_q=cu_seqlens_q, cu_seqlens_k=cu_seqlens_k,
930                max_seqlen_q=max_seqlen_in_batch_q, max_seqlen_k=max_seqlen_in_batch_k,
931                softmax_scale=softmax_scale, causal=causal,
932            )
933        except TypeError:
934            output = FLASH_KERNEL.flash_attn_varlen_func(
935                query_states, key_states, value_states,
936                cu_seqlens_q, cu_seqlens_k,
937                max_seqlen_in_batch_q, max_seqlen_in_batch_k,
938                0.0, softmax_scale, causal,
939            )
940        if isinstance(output, tuple):
941            return output[0]
942        return output
943    raise AssertionError(f"Unsupported kernels flash attention variant: {FLASH_KERNEL_VARIANT}")
944
945
946# Varlen flash attention runs only on real tokens. These helpers remove padding
947# before the kernel call and restore the original padded batch shape afterward.
948class IndexFirstAxis(torch.autograd.Function):
949    @staticmethod
950    def forward(ctx, input, indices) -> torch.Tensor:
951        ctx.save_for_backward(indices)
952        assert input.ndim >= 2
953        ctx.first_axis_dim, other_shape = input.shape[0], input.shape[1:]
954        second_dim = other_shape.numel()
955        return torch.gather(
956            rearrange(input, "b ... -> b (...)"), 0, indices.unsqueeze(1).expand(-1, second_dim)
957        ).reshape(-1, *other_shape)
958
959    @staticmethod
960    def backward(ctx, grad_output) -> Tuple[torch.Tensor, None]:
961        (indices,) = ctx.saved_tensors
962        assert grad_output.ndim >= 2
963        other_shape = grad_output.shape[1:]
964        grad_output = rearrange(grad_output, "b ... -> b (...)")
965        grad_input = torch.zeros(
966            [ctx.first_axis_dim, grad_output.shape[1]], device=grad_output.device, dtype=grad_output.dtype
967        )
968        grad_input.scatter_(0, indices.unsqueeze(1).expand(-1, grad_output.shape[1]), grad_output)
969        return grad_input.reshape(ctx.first_axis_dim, *other_shape), None
970
971
972class IndexPutFirstAxis(torch.autograd.Function):
973    @staticmethod
974    def forward(ctx, values, indices, first_axis_dim) -> torch.Tensor:
975        ctx.save_for_backward(indices)
976        assert indices.ndim == 1
977        assert values.ndim >= 2
978        output = torch.zeros(first_axis_dim, *values.shape[1:], device=values.device, dtype=values.dtype)
979        output[indices] = values
980        return output
981
982    @staticmethod
983    def backward(ctx, grad_output) -> Tuple[torch.Tensor, None, None]:
984        (indices,) = ctx.saved_tensors
985        return grad_output[indices], None, None
986
987
988index_first_axis = IndexFirstAxis.apply
989index_put_first_axis = IndexPutFirstAxis.apply
990
991
992def pad_input(hidden_states: torch.Tensor, indices: torch.Tensor, batch: int, seqlen: int) -> torch.Tensor:
993    output = index_put_first_axis(hidden_states, indices, batch * seqlen)
994    return rearrange(output, "(b s) ... -> b s ...", b=batch)
995
996
997def _unpad_input(
998    query_layer: torch.Tensor,
999    key_layer: torch.Tensor,
1000    value_layer: torch.Tensor,
1001    attention_mask_2d: torch.Tensor,
1002) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, Tuple[torch.Tensor, torch.Tensor], Tuple[int, int]]:
1003    batch_size, seq_len, num_heads, head_dim = query_layer.shape
1004    seqlens = attention_mask_2d.sum(dim=1).int()
1005    cu_seqlens = F.pad(seqlens.cumsum(0, dtype=torch.int32), (1, 0))
1006    max_seqlen = int(seqlens.max().item())
1007    indices = attention_mask_2d.flatten().nonzero(as_tuple=False).flatten()
1008    query_layer = index_first_axis(query_layer.reshape(batch_size * seq_len, num_heads, head_dim), indices)
1009    key_layer = index_first_axis(key_layer.reshape(batch_size * seq_len, num_heads, head_dim), indices)
1010    value_layer = index_first_axis(value_layer.reshape(batch_size * seq_len, num_heads, head_dim), indices)
1011    return query_layer, key_layer, value_layer, indices, (cu_seqlens, cu_seqlens), (max_seqlen, max_seqlen)
1012
1013
1014def kernels_flash_attention_func(
1015    query_states: torch.Tensor,
1016    key_states: torch.Tensor,
1017    value_states: torch.Tensor,
1018    attention_mask_2d: Optional[torch.Tensor] = None,
1019    causal: bool = False,
1020    softmax_scale: Optional[float] = None,
1021) -> torch.Tensor:
1022    """Public flash-attention entry point with optional padding handling.
1023
1024    `softmax_scale`:
1025        None -> kernel applies its default `1 / sqrt(head_dim)`.
1026        float -> kernel uses the given scale (pass 1.0 when Q is pre-scaled
1027        by the caller).
1028
1029    Caller contract: if a model family pre-scales Q by `1/sqrt(head_dim)`
1030    before calling this function (ESM2, DPLM, DPLM2, E1, and ESMFold do), pass
1031    `softmax_scale=1.0`. Otherwise the flash kernel applies its default scale
1032    again, yielding an effective `1/head_dim` scale that drifts across layers.
1033    """
1034    assert FLASH_KERNEL is not None, "Kernel Flash Attention is not available in this environment."
1035    if not causal and attention_mask_2d is not None:
1036        batch_size, q_len = query_states.shape[:2]
1037        (
1038            query_states, key_states, value_states,
1039            indices_q, (cu_seqlens_q, cu_seqlens_k), (max_seqlen_q, max_seqlen_k),
1040        ) = _unpad_input(query_states, key_states, value_states, attention_mask_2d)
1041        attn_output_unpad = _kernels_flash_varlen_forward(
1042            query_states=query_states, key_states=key_states, value_states=value_states,
1043            cu_seqlens_q=cu_seqlens_q, cu_seqlens_k=cu_seqlens_k,
1044            max_seqlen_in_batch_q=max_seqlen_q, max_seqlen_in_batch_k=max_seqlen_k,
1045            softmax_scale=softmax_scale,
1046        )
1047        return pad_input(attn_output_unpad, indices_q, batch_size, q_len)
1048    else:
1049        return _kernels_flash_forward(
1050            query_states=query_states, key_states=key_states, value_states=value_states,
1051            causal=causal, softmax_scale=softmax_scale,
1052        )
1053
1054
1055# User-facing backend strings resolve to this enum before attention dispatch.
1056class AttentionBackend(Enum):
1057    AUTO = "auto"
1058    KERNELS_FLASH = "kernels_flash"
1059    FLEX = "flex"
1060    SDPA = "sdpa"
1061
1062
1063VALID_ATTENTION_BACKENDS = tuple(b.value for b in AttentionBackend)
1064
1065
1066_BACKEND_CONFIRMED = False
1067
1068
1069def resolve_attention_backend(requested_backend: str) -> AttentionBackend:
1070    global _BACKEND_CONFIRMED
1071    assert requested_backend in VALID_ATTENTION_BACKENDS, (
1072        f"Unsupported attention backend: {requested_backend}. Expected one of {VALID_ATTENTION_BACKENDS}."
1073    )
1074    if requested_backend in (AttentionBackend.AUTO.value, AttentionBackend.KERNELS_FLASH.value):
1075        _ensure_flash_kernels_loaded()
1076    if requested_backend == AttentionBackend.AUTO.value:
1077        if FLASH_KERNEL is not None:
1078            resolved = AttentionBackend.KERNELS_FLASH
1079        elif flex_attention is not None:
1080            resolved = AttentionBackend.FLEX
1081        else:
1082            resolved = AttentionBackend.SDPA
1083    elif requested_backend == AttentionBackend.KERNELS_FLASH.value:
1084        assert FLASH_KERNEL is not None, "Kernels Flash Attention is not available in this environment."
1085        resolved = AttentionBackend.KERNELS_FLASH
1086    elif requested_backend == AttentionBackend.FLEX.value:
1087        assert flex_attention is not None, "Flex Attention is not available in this environment."
1088        resolved = AttentionBackend.FLEX
1089    elif requested_backend == AttentionBackend.SDPA.value:
1090        resolved = AttentionBackend.SDPA
1091    else:
1092        raise AssertionError(f"Unsupported attention backend: {requested_backend}")
1093    if not _BACKEND_CONFIRMED:
1094        print(f"Attention backend: config='{requested_backend}' -> resolved='{resolved.value}'")
1095        _BACKEND_CONFIRMED = True
1096    return resolved
1097
1098
1099@torch.compiler.disable
1100def get_attention_mask(
1101    effective_backend: AttentionBackend,
1102    batch_size: int,
1103    seq_len: int,
1104    device: torch.device,
1105    attention_mask: Optional[torch.Tensor] = None,
1106) -> Tuple[Optional[torch.Tensor], Optional[torch.Tensor], Optional[BlockMask]]:
1107    """Build padding masks once for all encoder layers.
1108
1109    Returns (attention_mask_2d, attention_mask_4d, flex_block_mask).
1110    """
1111    if attention_mask is None:
1112        return None, None, None
1113
1114    attention_mask_2d = attention_mask.bool()
1115
1116    if effective_backend == AttentionBackend.KERNELS_FLASH:
1117        return attention_mask_2d, None, None
1118
1119    if effective_backend == AttentionBackend.FLEX:
1120        assert create_block_mask is not None, "Flex attention backend requested but torch.create_block_mask is unavailable."
1121        valid_lens = attention_mask_2d.sum(dim=-1)
1122
1123        def mask_mod(batch_idx, head_idx, q_idx, kv_idx):
1124            return (q_idx < valid_lens[batch_idx]) & (kv_idx < valid_lens[batch_idx])
1125
1126        flex_block_mask = create_block_mask(mask_mod, batch_size, 1, seq_len, seq_len, device=device)
1127        return attention_mask_2d, None, flex_block_mask
1128
1129    # SDPA/manual masks only keys. Padding queries still attend to real keys, so
1130    # their outputs stay finite instead of softmaxing over all -inf scores.
1131    attention_mask_4d = attention_mask_2d[:, None, None, :]
1132    return attention_mask_2d, attention_mask_4d, None
1133
1134
1135def bool_to_additive_mask(
1136    bool_mask: torch.Tensor,
1137    dtype: torch.dtype,
1138) -> torch.Tensor:
1139    """Convert a bool mask (True = valid) to a float additive mask (0.0 valid, -inf invalid).
1140
1141    Why this exists: calling `bool_mask.masked_fill(bool_mask.logical_not(), float('-inf'))`
1142    directly on a bool tensor returns a bool tensor because `-inf` casts to `True`.
1143    That silently drops the mask. Always allocate a float tensor first, then fill it.
1144    This helper is the sanctioned way to build an SDPA additive mask from a bool validity mask.
1145    """
1146    assert bool_mask.dtype == torch.bool, (
1147        f"bool_to_additive_mask requires a bool tensor, got dtype={bool_mask.dtype}"
1148    )
1149    additive = torch.zeros_like(bool_mask, dtype=dtype)
1150    additive.masked_fill_(bool_mask.logical_not(), float("-inf"))
1151    return additive
1152
1153import typing as T
1154from dataclasses import dataclass, fields
1155
1156import torch
1157import torch.nn as nn
1158import torch.nn.functional as F
1159
1160
1161@dataclass
1162class TTTConfig:
1163    lr: float = 4e-4
1164    steps: int = 30
1165    ags: int = 16
1166    batch_size: int = 2
1167    mask_ratio: float = 0.15
1168    crop_size: int = 1024
1169    bert_leave_prob: float = 0.1
1170    bert_replace_prob: float = 0.1
1171    optimizer: str = "sgd"
1172    momentum: float = 0.0
1173    weight_decay: float = 0.0
1174    seed: int | None = 0
1175    lora_rank: int = 8
1176    lora_alpha: float = 32.0
1177    lora_target_replace_module: str | None = None
1178    lora_target_modules: tuple[str, ...] | None = None
1179    initial_state_reset: bool = True
1180    automatic_best_state_reset: bool = False
1181    eval_each_step: bool = False
1182    gradient_clip: bool = False
1183    gradient_clip_max_norm: float = 1.0
1184
1185    @classmethod
1186    def from_kwargs(cls, **kwargs: T.Any) -> "TTTConfig":
1187        valid_names = {field.name for field in fields(cls)}
1188        unknown_names = set(kwargs) - valid_names
1189        assert len(unknown_names) == 0, f"Unknown TTTConfig fields: {sorted(unknown_names)}"
1190        return cls(**kwargs)
1191
1192    def merged(self, overrides: T.Mapping[str, T.Any] | "TTTConfig" | None) -> "TTTConfig":
1193        if overrides is None:
1194            return self
1195        if isinstance(overrides, TTTConfig):
1196            return overrides
1197        values = {field.name: self.__dict__[field.name] for field in fields(self)}
1198        for name, value in overrides.items():
1199            assert name in values, f"Unknown TTTConfig field: {name}"
1200            values[name] = value

Showing the first 1,200 of 2584 lines. Download the file for the rest.