Questions, ideas, or simply want to discuss more about AIxBio? Join our Discord. Found a bug? Open an issue.
Nanomirrors gives you clean PyTorch implementations of state-of-the-art bio cell models behind one unified API and framework. Run inference, fine-tune, and train in ~10 lines of code.
Nanomirrors reimplements each model from scratch. Every implementation is small enough to read in one sitting and verified numerically against the original.
- One API, many models. Swap
scGPTforGeneformer,Stateand more (soon) by changing one string. - Readable. Each model is a single, self-contained file of a few hundred lines. No need to jump between twenty open tabs.
- Faithful. Implementations are tested for numerical equivalence against the official code and in reference to the paper.
- Iterate quickly. Want to test a new architecture, objective, or input encoding? Change a few lines and find out.
git clone https://github.com/windmirrorai/nanomirrors
cd nanomirrorsOption A: using uv
uv sync
source .venv/bin/activateOption B: using pip
python -m venv .venv && source .venv/bin/activate
pip install -e .Step-by-step tutorials to help you get started:
| Tutorial | Example Code | Colab Notebook |
|---|---|---|
| Cell embeddings | View | Open in Colab |
| Perturbation response prediction | View | - |
# scGPT cell embedding example
import numpy as np
from nanomirrors.auto import AutoModel, AutoInputEncoder
genes = ['DUX4L30', 'USP17L16P', 'RPL7P23', 'ZZZ3', 'ZSCAN9', 'VN1R104P']
exprs = np.array([[1.0, 0.0, 23.0, 6.0, 5.0, 4.0], [0.0, 0.0, 3.0, 7.0, 2.0, 1.0]])
model_name = "scGPT_human" # or "Geneformer-V2-104M", "Geneformer-V1-10M"
model = AutoModel.from_pretrained(model_name)
model.eval()
input_encoder = AutoInputEncoder.from_pretrained(model_name, gene_names=genes)
inputs = input_encoder.encode({"exprs": exprs})
cell_embeddings = model.encode_cell(**inputs) # (N, D)
gene_embeddings = model.encode_gene(**inputs) # (N, G, D)# Example: Tabula Sapiens lung data (downloaded automatically)
python tasks/embedding.py \
--model scGPT_human \
--output embeddings.npy
# Use a local .h5ad file
python tasks/embedding.py \
--model Geneformer-V2-104M \
--input <path to local .h5ad file> \
--output <path to save embeddings>
# Download a remote .h5ad file
python tasks/embedding.py \
--model scGPT_human \
--input_url <URL to a remote .h5ad file> \
--output <path to save embeddings>Supported embedding models: scGPT_human, Geneformer-V2-104M, and Geneformer-V1-10M.
Using your own dataset? See Bring your own data for the required format and annotations.
import torch
from nanomirrors.data import load_dataset
from nanomirrors.auto import AutoTrainerForPerturbationResponsePrediction
data = load_dataset("adamson2016") # load preprocessed perturbation dataset from HF
# Automatically intialize model, configs, and corresponding trainer
trainer = AutoTrainerForPerturbationResponsePrediction.from_model(
model_name="State",
n_genes=data.shape[1], # number of genes in data
device=torch.device("cuda")
)
history = trainer.train(train_data=data, val_data=None, checkpoint_path="./run")or use CLI
cd /src/nanomirrors
# Example: train/evaluate on adamson2016 dataset (preprocessed, downloaded automatically)
python tasks/perturbation_response_prediction.py \
--input_name adamson2016 \
--model State
--mode <train/test>
# Or on your custom dataset
python tasks/finetune_perturbation.py \
--input <path to local .h5ad file> \
--model scGPT
--mode <train/test>
# Or data from remote URL
python tasks/finetune_perturbation.py \
--input_url <path to local .h5ad file> \
--model Geneformer-V2-104M
--mode <train/test>
Supported PRP models: State, scGPT_human, Geneformer-V2-104M, and Geneformer-V1-10M.
Use an AnnData object with cells as rows and genes as columns, plus these annotations:
| Field | Contents |
|---|---|
adata.var["gene_symbol"] |
Gene symbols, e.g. "TP53" |
adata.var["gene_ensembl"] |
Ensembl gene IDs, e.g. "ENSG00000141510" |
adata.uns["organism"] |
Organism, e.g. "human" |
For perturbation response prediction, also include:
| Field | Contents |
|---|---|
adata.obs["perturbation"] |
Target gene, "+"-joined targets such as "TP53+MDM2", or "control" |
adata.obs["is_control"] |
Boolean indicating whether each cell is a control |
adata.uns["perturbation_method"] |
"CRISPRi", "CRISPRa", or "CRISPR-Cas9" |
adata.uns["covariates"] |
List of covariate column names in .obs, e.g. "[cell_type]"or [] if no cell covariates. Covariates influences cells grouping in training. |
# Assuming adata.obs["perturbation"], adata.var["gene_ensembl"] and adata.var["gene_symbols"] already populated:
adata.uns["organism"] = "human"
adata.uns["perturbation_method"] = "CRISPRi"
adata.uns["covariates"] = ["cell_type"] # Requires adata.obs["cell_type"]
adata.obs["is_control"] = adata.obs["perturbation"].eq("control")Include control cells and at least one non-control perturbation, with no missing perturbation labels. Example: preprocessed adamson perturbation h5ad.
Use the model identifier below when loading a model or trainer.
| Model | Model identifier | Embedding | PRP |
|---|---|---|---|
| scGPT | "scGPT_human" |
✅ | ✅ |
| Geneformer V2 | "Geneformer-V2-104M" |
✅ | ✅ |
| Geneformer V1 | "Geneformer-V1-10M" |
✅ | ✅ |
| State | "State" |
— | (ST+HVG) ✅ , (SE+ST) coming soon |
PRP = perturbation response prediction.
| Model | Paper | Verified vs. original | Weights license |
|---|---|---|---|
| scGPT | Cui et al., 2024 | Equivalent; 1.38× faster in our benchmark | scGPT license |
| Geneformer | Theodoris et al., 2023 | Equivalent | Geneformer license |
| State | State paper | Equivalent | State license |
Want another model or task supported? Open an issue.
Reimplementations are only useful if you can trust them. For every model we:
- Load the official checkpoint into both the reference code and the nanomirrors version.
- Compare outputs on fixed inputs. Equivalence is checked with
torch.allclose(ours, ref, atol=1e-3)
The nanomirrors code is released under the Apache 2.0.
Each wrapped model's pretrained weights and model-specific terms inherit the license of the original model. Check the table above before using a model commercially.
@software{nanomirrors2026,
author = {Danqi Liao, Ann Yu},
title = {nanomirrors: Minimal, readable state-of-the-art cell models},
year = {2026},
url = {https://github.com/windmirrorai/nanomirrors}
}Please also cite the original paper for each model you use.
