Genomic Foundation Models: Nucleotide Transformer, HyenaDNA, and Evo
When to Use
- Extracting sequence embeddings from a pretrained DNA/RNA language model for a downstream classifier (promoter, splice site, enhancer)
- Deciding between k-mer, BPE, or character-level tokenization for a genomic sequence task
- Choosing which foundation model fits a task: short-window classification vs. 100kb+ long-range regulatory context vs. sequence generation
- Building a lightweight k-mer baseline before paying for a large model's inference cost
- Explaining why 6-mer tokenization loses single-nucleotide resolution needed for splice-site or variant tasks
Version Compatibility
transformers >= 4.40, torch >= 2.1, Python >= 3.10
- Model checkpoints (HuggingFace Hub):
InstaDeepAI/nucleotide-transformer-500m-human-ref, LongSafari/hyenadna-tiny-1k-seqlen-hf, togethercomputer/evo-1-8k-base
trust_remote_code=True is required for HyenaDNA and Evo (custom modeling code, not yet in core transformers)
Prerequisites
pip install transformers torch numpy
- GPU strongly recommended for NT-500M+/Evo (T4 minimum, A100 for NT-2.5B/Evo-7B); CPU is fine for the toy k-mer baselines below
- Familiarity with tokenization concepts and basic PyTorch model inference
Tokenization Strategies
- Character-level (
A,C,G,T,N): highest resolution, longest sequences
- k-mer tokens (e.g. k=6): compressed vocab; k=6 -> 4096 tokens, k=8 -> 65,536 — prefer k<=6 for explicit k-mer tokenization
- BPE/subword: data-driven units (DNABERT-2)
- Nucleotide Transformer: overlapping 6-mers, stride=1, 4096-vocab, ~L/6 tokens per sequence — loses single-nucleotide resolution
- HyenaDNA: single-nucleotide tokens processed by a Hyena (implicit convolution) operator instead of self-attention; handles up to 1M nucleotides without O(L^2) attention cost
- Evo: single-nucleotide, trained on prokaryotic/viral genomes (not human) — use for microbial sequence design/scoring, not mammalian regulatory biology
Goal: get real contextual embeddings out of a pretrained genomic LLM instead of hand-built features.
Approach: load the tokenizer + model from the HuggingFace Hub, mask-average the last hidden state over real (non-padding) tokens.
import torch
from transformers import AutoTokenizer, AutoModelForMaskedLM
def embed_with_nucleotide_transformer(
sequences: list[str],
checkpoint: str = "InstaDeepAI/nucleotide-transformer-500m-human-ref",
) -> torch.Tensor:
"""Mean-pool the last hidden layer of Nucleotide Transformer over real tokens.
Returns a (n_sequences, hidden_dim) tensor of sequence embeddings.
"""
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForMaskedLM.from_pretrained(checkpoint, trust_remote_code=True)
model.eval()
tokens = tokenizer(sequences, return_tensors="pt", padding=True)["input_ids"]
attention_mask = tokens != tokenizer.pad_token_id
with torch.no_grad():
outputs = model(tokens, attention_mask=attention_mask, output_hidden_states=True)
hidden = outputs["hidden_states"][-1] # (batch, seq_len, hidden_dim)
mask = attention_mask.unsqueeze(-1)
mean_embeddings = (hidden * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1)
return mean_embeddings
# embeddings = embed_with_nucleotide_transformer(["ATTCCGATTCCG", "ATTTCTCTCTCTGAGATCGATCGAT"])
# embeddings.shape -> (2, 1280) for the 500M checkpoint
k-mer Baseline: Tokenize, Embed, Classify
Goal: a zero-GPU sanity-check baseline for a promoter/motif classification task, useful before committing to a foundation model.
Approach: count k-mer frequencies as a fixed-length vector, then classify with nearest-centroid — no training loop, no gradients.
import numpy as np
from collections import Counter
np.random.seed(7)
def kmers(seq: str, k: int = 6) -> list[str]:
"""Slide a window of size k across seq, returning all overlapping k-mers."""
seq = seq.upper()
return [seq[i:i + k] for i in range(len(seq) - k + 1)]
def kmer_embedding(seq: str, vocab: list[str], k: int = 3) -> np.ndarray:
"""Frequency-normalized k-mer count vector over a fixed vocab (order matters)."""
counts = Counter(kmers(seq, k))
vec = np.array([counts[v] for v in vocab], dtype=float)
return vec / (vec.sum() + 1e-9)
def random_dna(n: int) -> str:
return "".join(np.random.choice(list("ACGT"), size=n))
def inject_motif(seq: str, motif: str, pos: int) -> str:
return seq[:pos] + motif + seq[pos + len(motif):]
alphabet = ["A", "C", "G", "T"]
vocab_3 = [a + b + c for a in alphabet for b in alphabet for c in alphabet]
# Synthetic promoter task: half the sequences get a TATA box injected at pos=20
n_samples, length, motif = 120, 80, "TATAAA"
seqs, labels = [], []
for _ in range(n_samples):
s = random_dna(length)
if np.random.rand() < 0.5:
s = inject_motif(s, motif, pos=20)
labels.append(1)
else:
labels.append(0)
seqs.append(s)
X = np.stack([kmer_embedding(s, vocab_3, k=3) for s in seqs])
y = np.array(labels)
X_train, y_train = X[:90], y[:90]
X_test, y_test = X[90:], y[90:]
# Nearest-centroid: classify by distance to each class's mean embedding
c0 = X_train[y_train == 0].mean(axis=0)
c1 = X_train[y_train == 1].mean(axis=0)
d0 = ((X_test - c0) ** 2).sum(axis=1)
d1 = ((X_test - c1) ** 2).sum(axis=1)
pred = (d1 < d0).astype(int)
acc = (pred == y_test).mean()
print(f"Nearest-centroid probe accuracy: {acc:.3f}")
Goal: demonstrate why short context windows silently break long-range regulatory prediction.
Approach: a toy rule requiring two motifs 900+ bp apart; truncating the sequence drops one motif and flips the label.
def distal_interaction_label(seq: str) -> int:
"""Toy long-range rule: motif A near the start AND motif B near the end."""
has_a = "GATA" in seq[:120]
has_b = "CACC" in seq[-120:]
return int(has_a and has_b)
toy_long = random_dna(1000)
toy_long = inject_motif(toy_long, "GATA", 40)
toy_long = inject_motif(toy_long, "CACC", 920)
print("Long-range label:", distal_interaction_label(toy_long))
print("Truncated to 200 bp, the CACC motif at pos 920 is lost -> label flips to 0.")
Model Selection
| Task profile |
Preferred model |
| Short-window promoter/enhancer classification |
DNABERT-2 / Nucleotide Transformer |
| Distal regulatory context (100kb+) |
HyenaDNA |
| Prokaryotic sequence generation / scoring |
Evo |
| Variant effect on expression tracks |
Enformer / AlphaGenome |
| Splicing-focused interpretation |
SpliceAI |
Start with the k-mer baseline above; escalate to a foundation model only when accuracy or context length demands it — inference on NT-2.5B/Evo-7B needs an A100.
Pitfalls
- 6-mer tokenization loses single-base resolution: splice-site and single-variant tasks (GT/AG dinucleotide) need character-level or short k-mer models, not Nucleotide Transformer's default 6-mers
- Evo is prokaryotic/viral, not human: don't apply it to mammalian regulatory-genomics tasks
- Short windows silently drop long-range signal: a 200bp window can't see a 100kb enhancer-promoter interaction — check the model's effective context vs. the biology before trusting a negative prediction
trust_remote_code=True required: HyenaDNA and Evo ship custom modeling code on the Hub; review it before running in a shared/production environment
- Coordinate systems: BED is 0-based half-open, VCF/GFF are 1-based inclusive — mixing them causes off-by-one errors when mapping model outputs back to genome coordinates
- Multiple testing: apply FDR correction (Benjamini-Hochberg) when scoring thousands of candidate motifs/variants with any of these models
See Also
bio-structural-biology-modern-structure-prediction — coding-variant follow-up after a genomic-LLM hit (AlphaFold2/3)
esm — protein language model equivalent for amino-acid sequences
bio-variant-calling-variant-annotation — annotating variants before scoring them with a genomic LLM
bio-sequence-manipulation-motif-search — classical motif search as an alternative to embedding-based probes
1---2name: ai-science-genomic-llms3description: Embed DNA with genomic foundation models (Nucleotide Transformer, HyenaDNA, Evo) via HuggingFace transformers; k-mer tokenize, probe promoter motifs. Use for DNA LLMs, genomic embeddings, or NT/HyenaDNA/Evo choice.4---56# Genomic Foundation Models: Nucleotide Transformer, HyenaDNA, and Evo78## When to Use910- Extracting sequence embeddings from a pretrained DNA/RNA language model for a downstream classifier (promoter, splice site, enhancer)11- Deciding between k-mer, BPE, or character-level tokenization for a genomic sequence task12- Choosing which foundation model fits a task: short-window classification vs. 100kb+ long-range regulatory context vs. sequence generation13- Building a lightweight k-mer baseline before paying for a large model's inference cost14- Explaining why 6-mer tokenization loses single-nucleotide resolution needed for splice-site or variant tasks1516## Version Compatibility1718- `transformers` >= 4.40, `torch` >= 2.1, Python >= 3.1019- Model checkpoints (HuggingFace Hub): `InstaDeepAI/nucleotide-transformer-500m-human-ref`, `LongSafari/hyenadna-tiny-1k-seqlen-hf`, `togethercomputer/evo-1-8k-base`20- `trust_remote_code=True` is required for HyenaDNA and Evo (custom modeling code, not yet in core `transformers`)2122## Prerequisites2324- `pip install transformers torch numpy`25- GPU strongly recommended for NT-500M+/Evo (T4 minimum, A100 for NT-2.5B/Evo-7B); CPU is fine for the toy k-mer baselines below26- Familiarity with tokenization concepts and basic PyTorch model inference2728## Tokenization Strategies2930- **Character-level** (`A,C,G,T,N`): highest resolution, longest sequences31- **k-mer tokens** (e.g. k=6): compressed vocab; k=6 -> 4096 tokens, k=8 -> 65,536 — prefer k<=6 for explicit k-mer tokenization32- **BPE/subword**: data-driven units (DNABERT-2)33- **Nucleotide Transformer**: overlapping 6-mers, stride=1, 4096-vocab, ~L/6 tokens per sequence — loses single-nucleotide resolution34- **HyenaDNA**: single-nucleotide tokens processed by a Hyena (implicit convolution) operator instead of self-attention; handles up to 1M nucleotides without O(L^2) attention cost35- **Evo**: single-nucleotide, trained on prokaryotic/viral genomes (not human) — use for microbial sequence design/scoring, not mammalian regulatory biology3637**Goal:** get real contextual embeddings out of a pretrained genomic LLM instead of hand-built features.38**Approach:** load the tokenizer + model from the HuggingFace Hub, mask-average the last hidden state over real (non-padding) tokens.3940```python41import torch42from transformers import AutoTokenizer, AutoModelForMaskedLM434445def embed_with_nucleotide_transformer(46 sequences: list[str],47 checkpoint: str = "InstaDeepAI/nucleotide-transformer-500m-human-ref",48) -> torch.Tensor:49 """Mean-pool the last hidden layer of Nucleotide Transformer over real tokens.5051 Returns a (n_sequences, hidden_dim) tensor of sequence embeddings.52 """53 tokenizer = AutoTokenizer.from_pretrained(checkpoint)54 model = AutoModelForMaskedLM.from_pretrained(checkpoint, trust_remote_code=True)55 model.eval()5657 tokens = tokenizer(sequences, return_tensors="pt", padding=True)["input_ids"]58 attention_mask = tokens != tokenizer.pad_token_id5960 with torch.no_grad():61 outputs = model(tokens, attention_mask=attention_mask, output_hidden_states=True)62 hidden = outputs["hidden_states"][-1] # (batch, seq_len, hidden_dim)6364 mask = attention_mask.unsqueeze(-1)65 mean_embeddings = (hidden * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1)66 return mean_embeddings676869# embeddings = embed_with_nucleotide_transformer(["ATTCCGATTCCG", "ATTTCTCTCTCTGAGATCGATCGAT"])70# embeddings.shape -> (2, 1280) for the 500M checkpoint71```7273## k-mer Baseline: Tokenize, Embed, Classify7475**Goal:** a zero-GPU sanity-check baseline for a promoter/motif classification task, useful before committing to a foundation model.76**Approach:** count k-mer frequencies as a fixed-length vector, then classify with nearest-centroid — no training loop, no gradients.7778```python79import numpy as np80from collections import Counter8182np.random.seed(7)838485def kmers(seq: str, k: int = 6) -> list[str]:86 """Slide a window of size k across seq, returning all overlapping k-mers."""87 seq = seq.upper()88 return [seq[i:i + k] for i in range(len(seq) - k + 1)]899091def kmer_embedding(seq: str, vocab: list[str], k: int = 3) -> np.ndarray:92 """Frequency-normalized k-mer count vector over a fixed vocab (order matters)."""93 counts = Counter(kmers(seq, k))94 vec = np.array([counts[v] for v in vocab], dtype=float)95 return vec / (vec.sum() + 1e-9)969798def random_dna(n: int) -> str:99 return "".join(np.random.choice(list("ACGT"), size=n))100101102def inject_motif(seq: str, motif: str, pos: int) -> str:103 return seq[:pos] + motif + seq[pos + len(motif):]104105106alphabet = ["A", "C", "G", "T"]107vocab_3 = [a + b + c for a in alphabet for b in alphabet for c in alphabet]108109# Synthetic promoter task: half the sequences get a TATA box injected at pos=20110n_samples, length, motif = 120, 80, "TATAAA"111seqs, labels = [], []112for _ in range(n_samples):113 s = random_dna(length)114 if np.random.rand() < 0.5:115 s = inject_motif(s, motif, pos=20)116 labels.append(1)117 else:118 labels.append(0)119 seqs.append(s)120121X = np.stack([kmer_embedding(s, vocab_3, k=3) for s in seqs])122y = np.array(labels)123X_train, y_train = X[:90], y[:90]124X_test, y_test = X[90:], y[90:]125126# Nearest-centroid: classify by distance to each class's mean embedding127c0 = X_train[y_train == 0].mean(axis=0)128c1 = X_train[y_train == 1].mean(axis=0)129d0 = ((X_test - c0) ** 2).sum(axis=1)130d1 = ((X_test - c1) ** 2).sum(axis=1)131pred = (d1 < d0).astype(int)132133acc = (pred == y_test).mean()134print(f"Nearest-centroid probe accuracy: {acc:.3f}")135```136137**Goal:** demonstrate why short context windows silently break long-range regulatory prediction.138**Approach:** a toy rule requiring two motifs 900+ bp apart; truncating the sequence drops one motif and flips the label.139140```python141def distal_interaction_label(seq: str) -> int:142 """Toy long-range rule: motif A near the start AND motif B near the end."""143 has_a = "GATA" in seq[:120]144 has_b = "CACC" in seq[-120:]145 return int(has_a and has_b)146147148toy_long = random_dna(1000)149toy_long = inject_motif(toy_long, "GATA", 40)150toy_long = inject_motif(toy_long, "CACC", 920)151152print("Long-range label:", distal_interaction_label(toy_long))153print("Truncated to 200 bp, the CACC motif at pos 920 is lost -> label flips to 0.")154```155156## Model Selection157158| Task profile | Preferred model |159|---|---|160| Short-window promoter/enhancer classification | DNABERT-2 / Nucleotide Transformer |161| Distal regulatory context (100kb+) | HyenaDNA |162| Prokaryotic sequence generation / scoring | Evo |163| Variant effect on expression tracks | Enformer / AlphaGenome |164| Splicing-focused interpretation | SpliceAI |165166Start with the k-mer baseline above; escalate to a foundation model only when accuracy or context length demands it — inference on NT-2.5B/Evo-7B needs an A100.167168## Pitfalls169170- **6-mer tokenization loses single-base resolution**: splice-site and single-variant tasks (GT/AG dinucleotide) need character-level or short k-mer models, not Nucleotide Transformer's default 6-mers171- **Evo is prokaryotic/viral, not human**: don't apply it to mammalian regulatory-genomics tasks172- **Short windows silently drop long-range signal**: a 200bp window can't see a 100kb enhancer-promoter interaction — check the model's effective context vs. the biology before trusting a negative prediction173- **`trust_remote_code=True` required**: HyenaDNA and Evo ship custom modeling code on the Hub; review it before running in a shared/production environment174- **Coordinate systems**: BED is 0-based half-open, VCF/GFF are 1-based inclusive — mixing them causes off-by-one errors when mapping model outputs back to genome coordinates175- **Multiple testing**: apply FDR correction (Benjamini-Hochberg) when scoring thousands of candidate motifs/variants with any of these models176177## See Also178179- `bio-structural-biology-modern-structure-prediction` — coding-variant follow-up after a genomic-LLM hit (AlphaFold2/3)180- `esm` — protein language model equivalent for amino-acid sequences181- `bio-variant-calling-variant-annotation` — annotating variants before scoring them with a genomic LLM182- `bio-sequence-manipulation-motif-search` — classical motif search as an alternative to embedding-based probes