File size: 5,203 Bytes
b8232a0
 
cb634e7
 
 
 
 
 
 
b8232a0
cb634e7
 
 
 
 
 
ad5b35f
cb634e7
 
 
 
 
 
 
 
 
ad5b35f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cb634e7
 
 
 
 
 
 
 
 
 
 
 
 
 
ad5b35f
cb634e7
ad5b35f
cb634e7
 
 
 
 
 
 
ad5b35f
cb634e7
 
 
 
ad5b35f
 
cb634e7
 
 
 
 
 
ad5b35f
cb634e7
ad5b35f
 
 
 
 
cb634e7
 
 
ad5b35f
cb634e7
 
ad5b35f
cb634e7
 
 
 
ad5b35f
cb634e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ad5b35f
cb634e7
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
---
license: apache-2.0
tags:
  - biology
  - genomics
  - dna
  - masked-lm
library_name: transformers
pipeline_tag: fill-mask
---

# NucEngram

A **genomic language model (GLM)** for DNA sequences. It is a character-level
(single-nucleotide) model over the alphabet `A / C / G / T / N`, pretrained with
masked language modeling on genomic sequence, built on a ModernBERT encoder with
an **8192-nucleotide context window**.

It produces a **per-nucleotide contextual embedding** that you can pool and use
as features for downstream genomics tasks — promoter / splice-site / enhancer /
regulatory-element classification, sequence property prediction, etc. — either as
a frozen feature extractor or by fine-tuning.

The custom architecture ships with the repo, so load it with
`trust_remote_code=True`.

## Available sizes

Four sizes are released. They share the **same architecture, tokenizer and 8192-nt
context** and differ only in the transformer's **width (hidden size)** and
**depth (layers)** — i.e. capacity and compute:

| variant | hidden | layers | heads | parameters | download | how to load |
|---------|:------:|:------:|:-----:|:----------:|:--------:|-------------|
| `mini`  | 384    | 8      | 6     | ~150M      | 0.60 GB  | `subfolder="mini"` |
| `base`  | 512    | 22     | 16    | ~229M      | 0.92 GB  | *(default — repo root)* |
| `pro`   | 768    | 24     | 12    | ~364M      | 1.46 GB  | `subfolder="pro"` |
| `max`   | 1024   | 24     | 16    | ~542M      | 2.17 GB  | `subfolder="max"` |

Rule of thumb: **`mini`** is the fastest / lightest and a good default for large
screens or limited GPU memory; **`max`** gives the strongest representations at
the highest compute cost; `base` / `pro` sit in between. Same API for all.

```python
from transformers import AutoModel

# base (default, repo root)
base = AutoModel.from_pretrained("FreakingPotato/NucEngram", trust_remote_code=True)

# any other size via subfolder
mini = AutoModel.from_pretrained("FreakingPotato/NucEngram", subfolder="mini", trust_remote_code=True)
pro  = AutoModel.from_pretrained("FreakingPotato/NucEngram", subfolder="pro",  trust_remote_code=True)
maxm = AutoModel.from_pretrained("FreakingPotato/NucEngram", subfolder="max",  trust_remote_code=True)
```

## Install

```bash
pip install "transformers>=4.44" torch safetensors
```

## Quick start — embeddings

```python
from transformers import AutoModel

model = AutoModel.from_pretrained("FreakingPotato/NucEngram",
                                  trust_remote_code=True).eval()

# convenience helper: sequence(s) -> pooled embedding [B, hidden]
emb = model.embed(["ACGTACGTACGTGGTAAGT", "TTGCCGCGCGATCGATCG"])
print(emb.shape)   # torch.Size([2, 512])   (512 = base hidden size)
```

Per-nucleotide hidden states (for token-level tasks):

```python
ids, attention_mask = model.encode("ACGT...")     # char-level tokenizer, pad id 0
out = model(ids, attention_mask)
h = out.last_hidden_state                          # [B, T, hidden]
```

## Downstream task — fine-tuning

Add a pooling + linear head and fine-tune (or freeze `base` for linear probing).
Swap the `subfolder=` argument to choose a size:

```python
import torch, torch.nn as nn
from transformers import AutoModel

class SequenceClassifier(nn.Module):
    def __init__(self, n_classes, size=None, freeze_base=False):
        super().__init__()
        kw = {"trust_remote_code": True}
        if size:                       # None -> base (root); else "mini"/"pro"/"max"
            kw["subfolder"] = size
        self.base = AutoModel.from_pretrained("FreakingPotato/NucEngram", **kw)
        hidden = self.base.config.hidden_size
        if freeze_base:
            for p in self.base.parameters():
                p.requires_grad_(False)
        self.head = nn.Linear(hidden, n_classes)

    def forward(self, input_ids, attention_mask):
        h = self.base(input_ids, attention_mask).last_hidden_state   # [B, T, hidden]
        m = attention_mask.unsqueeze(-1).float()
        pooled = (h * m).sum(1) / m.sum(1).clamp(min=1.0)            # mean-pool
        return self.head(pooled)

clf = SequenceClassifier(n_classes=2, size="mini").train()
ids, am = clf.base.encode(["ACGT...", "GGGT..."])   # your batch of sequences
logits = clf(ids, am)
# ... standard cross-entropy training loop on your labelled dataset ...
```

For masked-LM scoring / filling:

```python
from transformers import AutoModelForMaskedLM
mlm = AutoModelForMaskedLM.from_pretrained("FreakingPotato/NucEngram",
                                           trust_remote_code=True).eval()
ids, am = mlm.encode("ACGTACGT")
logits = mlm(ids, am).logits        # [B, T, 9] over A/C/G/T/N + special tokens
```

## Details

| | |
|---|---|
| Backbone | ModernBERT encoder (see the size table above) |
| Context | up to 8192 nucleotides |
| Vocabulary | 9 tokens (A, C, G, T, N + pad/bos/eos/mask), char-level |
| Attention | `sdpa` by default (no flash-attn required) |
| Precision | fp32 weights (cast with `.half()` / `.bfloat16()` as you like) |

Input sequences are uppercase DNA strings; the built-in `encode()` maps
characters to ids and pads with id 0. Use `attention_mask` to ignore padding.