Skip to content

Commit fcd1918

Browse files
Merge pull request #1 from Open-Athena/proof-of-concept
Proof of concept
2 parents ad0f947 + 9dabb3c commit fcd1918

13 files changed

Lines changed: 420 additions & 2 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.cursorrules
2+
uv.lock
13
# Byte-compiled / optimized / DLL files
24
__pycache__/
35
*.py[codz]

.pre-commit-config.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
repos:
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
3+
rev: v0.12.11
4+
hooks:
5+
- id: ruff
6+
args: [--fix]
7+
- id: ruff-format
8+
9+
- repo: https://github.com/pre-commit/mirrors-mypy
10+
rev: v1.17.1
11+
hooks:
12+
- id: mypy
13+
exclude: ^(tests/|examples/)
14+
entry: uv run mypy

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.13

README.md

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,64 @@
1-
# biofoundation
2-
Biological foundation models
1+
![BioFoundation Logo](docs/source/_static/logo.png)
2+
3+
# BioFoundation
4+
5+
An ecosystem for biological foundation models.
6+
7+
## Installation
8+
9+
Create and activate a virtual environment:
10+
11+
```bash
12+
uv venv
13+
source .venv/bin/activate
14+
```
15+
16+
Install the package:
17+
18+
```bash
19+
uv pip install -e .
20+
```
21+
22+
## Additional Dependencies
23+
24+
```bash
25+
# PlantCad1
26+
uv pip install -e .[mamba]
27+
```
28+
29+
## Usage
30+
31+
Run the example script:
32+
33+
```bash
34+
source .venv/bin/activate
35+
python examples/plantcad_evolutionary_constraint.py
36+
```
37+
38+
## Development Setup
39+
40+
To set up the development environment with linting, formatting, type checking, and testing:
41+
42+
```bash
43+
# Install development dependencies
44+
uv pip install --group dev
45+
46+
# Install both main package and dev tools
47+
uv pip install -e . --group dev
48+
```
49+
50+
## Development Tools
51+
52+
### Running Quality Checks
53+
54+
```bash
55+
# Run all pre-commit hooks (linting, formatting, type checking)
56+
pre-commit run --all-files
57+
```
58+
59+
### Running Tests
60+
61+
```bash
62+
# Run all tests
63+
pytest
64+
```

biofoundation/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""An ecosystem for biological foundation models.
2+
3+
This package provides tools for working with biological foundation models,
4+
including data transformation, model wrappers, and inference utilities.
5+
"""
6+
7+
try:
8+
from importlib.metadata import version
9+
10+
__version__ = version("biofoundation")
11+
except ImportError:
12+
__version__ = "unknown"

biofoundation/data.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from transformers import PreTrainedTokenizerBase
2+
from typing import Any
3+
4+
5+
def transform_reflogprob_mlm(
6+
example: dict[str, Any],
7+
tokenizer: PreTrainedTokenizerBase,
8+
pos: int,
9+
seq_col: str = "seq",
10+
) -> dict[str, Any]:
11+
"""Transform a sequence example for reference log probability MLM inference.
12+
13+
This function prepares a sequence example for masked language modeling by:
14+
1. Tokenizing the input sequence
15+
2. Masking a specific position in the sequence
16+
3. Recording the reference token at that position
17+
18+
Args:
19+
example: Dictionary containing the sequence data. Must have a key matching
20+
`seq_col` that contains the input sequence.
21+
tokenizer: HuggingFace tokenizer for converting text to token IDs.
22+
pos: Position in the sequence to mask (0-indexed).
23+
seq_col: Key in the example dictionary that contains the sequence.
24+
Defaults to "seq".
25+
26+
Returns:
27+
Dictionary with three keys:
28+
- input_ids_BL: Token IDs with the specified position masked
29+
- pos_B: The masked position
30+
- ref_B: The reference token ID that was at the masked position
31+
32+
Example:
33+
>>> example = {"seq": "ATCG"}
34+
>>> tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
35+
>>> result = transform_reflogprob_mlm(example, tokenizer, 1)
36+
>>> print(result)
37+
{'input_ids_BL': tensor([...]), 'pos_B': 1, 'ref_B': 3}
38+
"""
39+
input_ids = tokenizer(example[seq_col], return_tensors="pt")["input_ids"][0]
40+
ref = input_ids[pos].item()
41+
input_ids[pos] = tokenizer.mask_token_id
42+
return dict(input_ids_BL=input_ids, pos_B=pos, ref_B=ref)

biofoundation/inference.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import datasets
2+
import tempfile
3+
import torch.nn as nn
4+
from transformers import Trainer, TrainingArguments
5+
from typing import Any
6+
7+
8+
def run_inference(
9+
model: nn.Module,
10+
dataset: datasets.Dataset,
11+
**kwargs: Any,
12+
) -> Any:
13+
"""Run inference on a dataset using a trained model.
14+
15+
Args:
16+
model: A trained PyTorch model that can be used with the HuggingFace Trainer.
17+
dataset: HuggingFace dataset to run inference on. The dataset should be
18+
compatible with the model's expected input format.
19+
**kwargs: Additional keyword arguments to pass to TrainingArguments.
20+
Common options include:
21+
- per_device_eval_batch_size: Batch size for evaluation
22+
- dataloader_num_workers: Number of workers for data loading
23+
- torch_compile: Whether to use torch.compile for faster inference
24+
- bf16_full_eval: Whether to use bf16 for evaluation
25+
26+
Returns:
27+
The model's predictions on the dataset. The exact format depends on the
28+
model and dataset, but typically includes probabilities or embeddings.
29+
"""
30+
training_args = TrainingArguments(
31+
output_dir=tempfile.TemporaryDirectory().name,
32+
**kwargs,
33+
)
34+
trainer = Trainer(model=model, args=training_args)
35+
return trainer.predict(test_dataset=dataset).predictions

biofoundation/model.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import torch
2+
import torch.nn as nn
3+
import torch.nn.functional as F
4+
from torch import Tensor
5+
from jaxtyping import Float, Int
6+
7+
8+
class RefLogProbMLM(nn.Module):
9+
"""Reference Log Probability Masked Language Model wrapper.
10+
11+
This class wraps a pre-trained language model to compute reference log probabilities
12+
for masked language modeling tasks. It takes a sequence with a masked position
13+
and returns the log probability of the reference token at that position.
14+
15+
The model expects input tensors with specific shapes:
16+
- input_ids_BL: Batch of token ID sequences [batch_size, sequence_length]
17+
- pos_B: Positions to evaluate for each sequence in the batch
18+
- ref_B: Reference token IDs to compute log probabilities for
19+
20+
Attributes:
21+
model: The underlying language model that provides logits
22+
"""
23+
24+
def __init__(self, model: nn.Module):
25+
super().__init__()
26+
self.model = model
27+
28+
def forward(
29+
self,
30+
input_ids_BL: Int[Tensor, "B L"],
31+
pos_B: Int[Tensor, " B"],
32+
ref_B: Int[Tensor, " B"],
33+
) -> Float[Tensor, " B"]:
34+
"""Forward pass to compute reference log probabilities.
35+
36+
This method:
37+
1. Runs the input through the language model to get logits
38+
2. Extracts logits at the specified positions
39+
3. Computes log probabilities using softmax
40+
4. Returns the log probability of the reference tokens
41+
42+
Args:
43+
input_ids_BL: Batch of token ID sequences with shape [B, L] where
44+
B is batch size and L is sequence length
45+
pos_B: Positions to evaluate for each sequence in the batch with
46+
shape [B] where each element is the position index
47+
ref_B: Reference token IDs to compute log probabilities for with
48+
shape [B] where each element is the token ID
49+
50+
Returns:
51+
Log probabilities of the reference tokens at the specified positions
52+
with shape [B] where B is the batch size
53+
"""
54+
B, L = input_ids_BL.shape
55+
batch_indices_B = torch.arange(B)
56+
logits_BLV = self.model(input_ids_BL).logits
57+
logits_BV = logits_BLV[batch_indices_B, pos_B]
58+
logprobs_BV = F.log_softmax(logits_BV, dim=-1)
59+
res_B = logprobs_BV[batch_indices_B, ref_B]
60+
return res_B

docs/source/_static/logo.png

200 KB
Loading
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from biofoundation.data import transform_reflogprob_mlm
2+
from biofoundation.model import RefLogProbMLM
3+
from biofoundation.inference import run_inference
4+
from datasets import load_dataset
5+
from functools import partial
6+
import numpy as np
7+
from sklearn.metrics import roc_auc_score
8+
from transformers import AutoTokenizer, AutoModelForMaskedLM
9+
10+
11+
model_name = "kuleshov-group/PlantCaduceus_l20"
12+
tokenizer = AutoTokenizer.from_pretrained(model_name)
13+
model = AutoModelForMaskedLM.from_pretrained(model_name, trust_remote_code=True)
14+
15+
dataset = load_dataset(
16+
"plantcad/evolutionary-constraint-example",
17+
"10k",
18+
split="validation",
19+
)
20+
label = np.array(dataset["label"])
21+
22+
dataset = dataset.map(
23+
partial(
24+
transform_reflogprob_mlm,
25+
tokenizer=tokenizer,
26+
pos=255,
27+
seq_col="sequences",
28+
),
29+
remove_columns=dataset.column_names,
30+
num_proc=8,
31+
)
32+
33+
pred = run_inference(
34+
RefLogProbMLM(model),
35+
dataset,
36+
per_device_eval_batch_size=256,
37+
torch_compile=False,
38+
bf16_full_eval=True,
39+
dataloader_num_workers=8,
40+
)
41+
42+
print(f"{roc_auc_score(label, pred)=}")

0 commit comments

Comments
 (0)