Benchmark design¶
The dataset¶
The zero-shot benchmark uses Tabula Sapiens v2 — released through
CZ CELLxGENE Discover as collection
10df7690-6d10-4029-a47e-0f071bb2df83 under CELLxGENE schema 5.2.0, and distributed as
per-tissue benchmarking files that can be downloaded with one command (below).
Property |
Value |
|---|---|
Tissues |
26 |
Cells |
548,977 |
Cells per tissue |
2,044 – 72,290 |
Cell types per tissue |
14 – 32 |
Genes per tissue |
21,808 – 37,066 |
Tissues with multiple technical batches |
19 |
Using 26 separate tissues rather than one pooled object is deliberate. Each tissue is an independent evaluation with its own cell-type composition and its own batch structure, which turns a single benchmark into 26 replicates — and lets the analysis ask whether a method’s advantage is consistent or tissue-specific.
Downloading it¶
The 26 per-tissue files are distributed through CZI’s public benchmark-data bucket, with
anonymous access — about 25 GB in total (pip install awscli if aws is not on your
machine):
aws s3 sync --no-sign-request \
s3://cz-benchmarks-data/datasets/v1/cell_atlases/Homo_sapiens/Tabula_Sapiens_v2/ raw/
One .h5ad per tissue, named homo_sapiens_10df7690-…_<Tissue>_v2_curated.h5ad. The
sample_id in every result table of this site is exactly this filename, so what you
download is byte-for-byte what the benchmark ran on; the preparation below changes
metadata only.
Tissues¶
Bladder Ear Heart Liver Muscle Salivary_Gland Spleen Thymus Uterus
Blood Eye Large_Intestine Lung Ovary Skin Stomach Tongue Vasculature
Bone_Marrow Fat Lymph_Node Mammary Prostate Small_Intestine Testis Trachea
Seven have only a single technical batch — Eye, Lung, Mammary, Prostate, Skin, Testis and Uterus — so batch-mixing metrics are undefined for them. The remaining 19 carry the batch comparison.
Preparing the inputs¶
Raw CELLxGENE files do not satisfy the input contract directly. The preparation is metadata-only: no quality control, no normalisation, no gene filtering, so the models receive the data as deposited.
Four transformations:
1. Gene identifiers. CELLxGENE indexes by Ensembl ID and carries display names in
var["feature_name"]. The benchmark uses feature_name as the primary identifier where it
is a usable HGNC symbol, and falls back to the Ensembl ID where it is not — an
Ensembl-looking or version-suffixed feature_name is treated as no symbol at all. The
result is written to the index, to var["gene_symbol"], and the Ensembl ID to
var["ensembl_id"]. Preparation fails loudly if the derived identifiers are not
unique, rather than silently deduplicating.
2. Cell identifiers. obs["barcode"] is set from the original observation index.
3. Batch definition. This is the substantive choice:
adata.obs["batch_id"] = (
adata.obs["assay"].astype(str) + "|" + adata.obs["donor_id"].astype(str)
)
Batch is assay crossed with donor, not donor alone. A donor-only definition would confound batch with biology — donors differ genetically and clinically, not just technically — and any method that removed it would be removing signal. Crossing with assay means a tissue only counts as multi-batch when the same biological material was measured on more than one platform.
4. Raw slot. The AnnData raw slot is deleted; X is left untouched.
See also
The same protocol in general form, with runnable code, is on the input format page.
Generating embeddings¶
One run per method and tissue. Every method sees byte-identical input files — the whole point of the framework.
for m in pca scvi scgpt geneformer scfoundation scbert sccello uce scimilarity \
scprint langcell cellfm cellplm cellama genept c2s; do
for f in prepared/*.h5ad; do
scfoundry embed --method "$m" --data "$f" --gpu 0
done
done
for m in scgpt_integrated scvi_denovo harmony seurat_cca seurat_rpca; do
for f in prepared/*.h5ad; do
scfoundry embed --method "$m" --data "$f" --batch-key batch_id --gpu 0
done
done
Defaults are used throughout — no per-method tuning, no per-tissue tuning. A benchmark where each method is tuned by a different amount measures the tuning effort, not the models.
Note
The full sweep is 20 methods × 26 tissues = 520
embedding runs. Every run has its own directory and log, so a failure costs one
method-tissue pair rather than the sweep; on a cluster, add --profile slurm and let the
scheduler place them. See Running on an HPC cluster.
pca is included as the reference point. It has no pretraining, no checkpoint, and no
GPU requirement, and it is the standard every foundation model has to clear to justify
its cost.
Computing metrics¶
One call per method scores all 26 embeddings:
for m in $(ls results/embeddings); do
scfoundry benchmark --embedding results/embeddings/$m --batch-key batch_id
done
Biological conservation is scored on every tissue against obs["cell_type"]; batch mixing
against obs["batch_id"] wherever it has more than one level, which leaves the seven
single-batch tissues with NaN batch metrics rather than misleading ones. The wide tables
in results/benchmark/ are, row for row, the tables offered for
download.
Measuring geometry¶
Likewise one call per method, pairing each embedding with the prepared input of the same name:
for m in $(ls results/embeddings); do
scfoundry geometry --embedding results/embeddings/$m --data prepared/
done
Tissues larger than 20,000 cells are subsampled once, with a seed derived from the tissue
name, so every method is measured on the same cells. The defaults (--max-cells 20000,
--seed 0) are the paper’s settings.
Aggregating¶
Within each tissue every method is ranked on every metric, the ranks are averaged within four families, the family ranks are weighted, and the per-tissue aggregate ranks are averaged over tissues. Definitions, weights and the code are on the metrics page.
Two properties of the aggregation are worth stating here, because they shape how results should be read.
Single-batch tissues are not penalised. When the batch-mixing family is undefined, it is dropped and the remaining three weights are renormalised. A tissue with one batch is scored on biology alone rather than being scored as if it had failed at batch correction.
Ranking is per tissue and per metric, then aggregated. Methods are ranked within each tissue on each metric and the ranks are combined, rather than averaging raw metric values. Raw values are not comparable between tissues — a tissue with 32 cell types yields systematically lower ARI than one with 14, regardless of method — so averaging them would let tissue difficulty dominate the result; and they are not comparable between metrics either, so ranking per metric keeps a wide-ranging metric from dominating its family.
Extending the benchmark to your own data¶
The protocol transfers directly. Three things to keep if you want your numbers to mean the same thing:
Prepare identically. Metadata only. Any quality control you apply must be applied before preparation and identically for every method.
Use defaults. Tuning one method and not others produces a comparison of your effort rather than of the models.
Include PCA. It is cheap, and without a reference a table of foundation-model scores has no scale. If nothing beats PCA on your data, that is the finding.
See also
Evaluation metrics — what each metric measures and how families are weighted.
Input data format — the contract prepared files must satisfy.
Citation — citing Tabula Sapiens and CZ CELLxGENE.