Transformers
Safetensors
genomics
human-genetics
genotype
snp
modernbert
masked-language-modeling
uk-biobank
allele-dosage
Instructions to use ccjsj/SNPBERT with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ccjsj/SNPBERT with Transformers:
# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("ccjsj/SNPBERT", device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 7,081 Bytes
08840d7 | 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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | ---
license: mit
library_name: transformers
inference: false
tags:
- genomics
- human-genetics
- genotype
- snp
- modernbert
- masked-language-modeling
- uk-biobank
- allele-dosage
---
# SNPBERT
Self-supervised transformer that learns individual-level representations from unlabeled
genotype data. Each person is encoded as an ordered sequence of **rsID tokens** β one per
locus carrying at least one alternate allele β and the model is pretrained by masked
language modeling (masked rsID prediction) on **485,923 UK Biobank participants across
13,615 SNPs**. Built on the [ModernBERT](https://huggingface.co/docs/transformers/model_doc/modernbert)
architecture; its alternating local/global attention captures both short-range linkage
disequilibrium and genome-wide structure in a single forward pass.
**Code / reproduction:** https://github.com/BioLab-D/SNPBERT
## Two backbones in this repo
This repo ships **two pretrained backbones** as subfolders:
| Subfolder | Model | Genotype encoding | Load with | Masked-SNP acc. |
|---|---|---|---|---|
| [`dominant/`](./dominant) | `ModernBertForMaskedLM` | presence/absence (β₯1 alt allele) | `AutoModel` + `subfolder="dominant"` | 0.785 |
| [`dosage/`](./dosage) | `ModernBertWithDosage` (base + dosage embedding) | het (1) vs. hom-alt (2) | **custom class** + `subfolder="dosage"` | 0.787 |
The **dosage** backbone adds an additive per-token dosage embedding on top of the dominant
architecture, so it distinguishes heterozygous from homozygous-alternate genotypes.
- **Architecture:** ModernBERT β 22 layers, hidden 768, 12 heads, intermediate 1152,
global attention every 3rd layer, max position 9936, vocab 13,620.
- **Precision:** bfloat16.
> **Precision note:** weights are bfloat16 (trained on GPU). On **CPU**, cast to float32
> (`.float()`) β CPU LayerNorm does not support bfloat16. On GPU, use
> `torch_dtype=torch.bfloat16`.
## Tokenizer conventions (shared by both)
The vocabulary reserves the **first 5 ids** for special tokens, then SNP column *i* maps
to token id `i + 5`:
```
0=[UNK] 1=[SEP] 2=[PAD] 3=[CLS] 4=[MASK] 5..=SNP tokens (e.g. rs11260596_T)
```
Each SNP token is `rsID_allele`. An individual is a sequence
`[CLS] <SNP tokens for loci with β₯1 alt allele> [SEP]`.
---
## Usage β dominant backbone
Standard `ModernBertForMaskedLM`; loads directly with `AutoModel`.
```python
import torch
from transformers import AutoModel, AutoTokenizer
device = "cuda" if torch.cuda.is_available() else "cpu"
tok = AutoTokenizer.from_pretrained("ccjsj/SNPBERT", subfolder="dominant")
model = AutoModel.from_pretrained("ccjsj/SNPBERT", subfolder="dominant",
attn_implementation="sdpa").to(device)
model = model.bfloat16() if device == "cuda" else model.float()
model.eval()
# One individual: [CLS] + SNP tokens (loci with >=1 alt allele) + [SEP]
snps = ["rs11260596_T", "rs6603782_T", "rs11523819_A"]
ids = [tok.cls_token_id] + tok.convert_tokens_to_ids(snps) + [tok.sep_token_id]
input_ids = torch.tensor([ids], device=device)
attention_mask = torch.ones_like(input_ids)
with torch.no_grad():
out = model(input_ids=input_ids, attention_mask=attention_mask)
cls_embedding = out.last_hidden_state[:, 0] # (1, 768)
```
These `[CLS]` embeddings feed the paper's downstream tasks (population-structure
clustering, disease-risk AMIL, variant identification).
---
## Usage β dosage backbone
### β οΈ Not a plain `AutoModel` β custom class required
The `dosage/` checkpoint is **not** a standard `ModernBertForMaskedLM`. Its flat state
dict is:
```
base.* # a ModernBertForMaskedLM (138 tensors)
dosage_emb.weight # nn.Embedding(3, 768) (the dosage embedding)
```
`config.json` still declares `architectures: ["ModernBertForMaskedLM"]`, so loading with
`AutoModel.from_pretrained(...)` will **silently drop `dosage_emb.weight`**. You must
rebuild the `ModernBertWithDosage` wrapper. Its forward adds the dosage embedding to the
token embeddings **before** the encoder:
```
inputs_embeds = base.get_input_embeddings()(input_ids) + dosage_emb(dosage_ids)
```
`dosage_ids` is per-token in `{0, 1, 2}` β `0` at special positions, `1` (heterozygous) /
`2` (homozygous-alternate) at SNP positions.
### Option A β install the SNPBERT package
```bash
pip install "git+https://github.com/BioLab-D/SNPBERT.git"
```
```python
import torch
from huggingface_hub import snapshot_download
from snpbert.model import load_dosage_model
root = snapshot_download("ccjsj/SNPBERT") # downloads both subfolders
ckpt = f"{root}/dosage"
device = "cuda" if torch.cuda.is_available() else "cpu"
model = load_dosage_model(ckpt, ckpt, num_dosage=3, attn_impl="sdpa").to(device)
model = model.bfloat16() if device == "cuda" else model.float() # CPU needs fp32
model.eval()
```
### Option B β self-contained (no repo install)
```python
import torch, torch.nn as nn
from transformers import AutoConfig, AutoModelForMaskedLM, AutoTokenizer
from safetensors.torch import load_file
from huggingface_hub import hf_hub_download
class ModernBertWithDosage(nn.Module):
def __init__(self, base, num_dosage=3):
super().__init__()
self.base = base
self.dosage_emb = nn.Embedding(num_dosage, base.config.hidden_size)
def forward(self, input_ids, dosage_ids=None, attention_mask=None, labels=None):
emb = self.base.get_input_embeddings()(input_ids)
if dosage_ids is not None:
emb = emb + self.dosage_emb(dosage_ids)
return self.base(inputs_embeds=emb, attention_mask=attention_mask, labels=labels)
repo = "ccjsj/SNPBERT"
cfg = AutoConfig.from_pretrained(repo, subfolder="dosage")
cfg._attn_implementation = "sdpa"
model = ModernBertWithDosage(AutoModelForMaskedLM.from_config(cfg), num_dosage=3)
sd = load_file(hf_hub_download(repo, "model.safetensors", subfolder="dosage"))
model.load_state_dict(sd, strict=True) # keys: base.* + dosage_emb.weight
model.float().eval()
```
### Forward with dosage
```python
tok = AutoTokenizer.from_pretrained("ccjsj/SNPBERT", subfolder="dosage")
snps = ["rs11260596_T", "rs6603782_T", "rs11523819_A"]
ids = [tok.cls_token_id] + tok.convert_tokens_to_ids(snps) + [tok.sep_token_id]
input_ids = torch.tensor([ids])
attn = torch.ones_like(input_ids)
# per-token dosage: 0 at [CLS]/[SEP]; 1=het, 2=hom-alt at SNP positions
dosage_ids = torch.tensor([[0, 1, 2, 1, 0]])
with torch.no_grad():
out = model(input_ids=input_ids, dosage_ids=dosage_ids, attention_mask=attn)
logits = out.logits # (1, seq_len, 13620)
```
---
## Downstream use
The frozen backbone feeds three analyses in the paper: population-structure clustering
(matching PCA), disease-risk modeling with a sparse-gated attention-MIL head, and
attention-based variant prioritization. Pipelines and a runnable synthetic-data demo are
in the [GitHub repo](https://github.com/BioLab-D/SNPBERT).
## License
MIT. Trained on UK Biobank data under approved access; use of derived representations
must comply with UK Biobank terms.
|