In this post we'll take a single gene, pull it from six species that diverged up to 450 million years ago, score the DNA with a language model, translate it to protein, fold it into 3D structures, and build a phylogenetic tree. One pipeline in pure Python. The key takeaway: protein structure is more conserved than sequence. Even when DNA changes significantly across species, the 3D fold often stays remarkably similar, because it's the shape, not the exact sequence, that keeps the organism alive. Until relatively recently, getting a 3D structure required months of wet-lab work per protein. Now Carbon and ESMFold let you run the same analysis across six species in minutes, turning what used to be a PhD project into a single pipeline run.
The full code is on GitHub. It runs on three curated gene sets (insulin, hemoglobin, p53) or any custom gene set you provide.

Why cross-species gene comparison matters
Every species on Earth shares a common ancestor, and we carry the proof in our DNA. The insulin gene in a human and a zebrafish diverged roughly 450 million years ago, but they're still recognizably the same gene. Insulin is so critical to survival that evolution can't change it much without killing the organism. For example fish insulin can still lower blood sugar in mammals.
This principle, that function constrains change, shows up everywhere in biology. But it shows up differently at different levels. DNA accumulates “silent” mutations (changes that don't alter the protein) freely, so DNA diverges faster than protein. And protein sequence diverges faster than protein structure, because many different sequences can fold into the same shape. Running the comparison across all three levels reveals how evolution actually works at the molecular level.
This isn't just academic. Understanding which parts of a gene are conserved tells drug designers which protein regions are functionally essential and likely to be similar across species, which matters for animal model selection and drug target validation. It tells geneticists which mutations are likely pathogenic (if the position is conserved, changing it is probably bad). And it gives computational biologists a way to validate their models: if your structure predictor shows the same fold across divergent species, it's probably capturing real biology.
The tools that make this analysis practical are recent. HuggingFace's Carbon is a DNA language model that scores how "natural" a sequence looks. Without it, you'd only be comparing raw sequence similarity, which treats every position equally. Carbon adds a learned signal: it can tell you that certain mutations matter more than others because they make the sequence look less like real DNA. That extra dimension helps distinguish functional divergence from neutral drift. Meta's ESMFold predicts 3D protein structure directly from the amino acid sequence. Combining them in a single pipeline gives you the full picture: DNA likelihood, sequence similarity, and structural comparison across species, all in one run.
Building the cross-species comparison pipeline
This is the kind of pipeline where AI orchestration pays off. Carbon-3B and ESMFold each need a GPU and significant memory, but loading gene sequences and building phylogenetic trees don't. Without proper AI orchestration, you either keep a GPU running the whole time (expensive) or manually shuffle data between scripts. Flyte lets you assign resources per task, so you only use (and pay for) a GPU when you're actually running a model. If ESMFold fails on a long protein (it can be memory-hungry), you can re-run from that step without re-scoring everything with Carbon. Every run is versioned, so when you're comparing results across gene sets or model versions, you can trace each result back to exactly what produced it. And the whole thing is pure Python: no YAML configs, no shell glue, no separate orchestration language.
The full code is on GitHub.
Five stages: load homologous gene sequences, score with Carbon, align sequences and compute pairwise similarity, fold proteins with ESMFold, generate an evolutionary summary.
Both environments are defined in `config.py`. The GPU environment gets 32Gi of memory to fit both Carbon-3B and ESMFold. The CPU environment is lightweight and handles data loading and summary generation. Flyte builds the container images from your requirements.txt list automatically, no separate Dockerfiles needed.
The pipeline task orchestrates all five stages. Each `await` passes typed data between tasks. Flyte handles the serialization and transfer automatically. The pipeline returns both the comparison data and the structure data, so downstream analysis or visualization tools can consume either. We'll define each of the tasks called here in the sections that follow.

The gene sets
The pipeline ships with three curated gene sets, each chosen to show different evolutionary pressures. You can also pass your own sequences as custom JSON.
Each gene set tells a different story. Insulin is the textbook example of purifying selection: so critical that even fish and human versions can be “interchangeable”. Hemoglobin beta is one of the most studied genes in molecular evolution, and sequence differences between species power the "molecular clock" hypothesis that underpins modern phylogenetics. The p53 set is the most surprising: it includes an elephant, which has 20 copies of this tumor suppressor gene compared to our single copy. This may explain why elephants have extremely low cancer rates despite their massive body size, a puzzle known as Peto's paradox.
Understanding the biology: translation and sequence identity
Before diving into the pipeline tasks, it helps to understand the biology utilities that underpin the analysis. DNA is read in groups of three letters called codons. Each codon maps to one amino acid, and amino acids are the building blocks of proteins. The codon table below is that mapping. It's the key to understanding why protein is more conserved than DNA: the mapping is redundant. Four different codons all encode the same amino acid Leucine (CTT, CTC, CTA, CTG). A mutation that changes CTT to CTC changes the DNA but not the protein. These synonymous mutations accumulate freely over evolutionary time, which is why DNA diverges faster than protein for coding genes.
The `_translate` function reads DNA in triplets (codons) and converts each to an amino acid, stopping at a stop codon (*). The `_gc_content` function measures the proportion of G and C bases, which varies between species and can affect how DNA language models score sequences. The `_sequence_identity` function uses Needleman-Wunsch global alignment, a standard algorithm in bioinformatics for comparing two sequences. It inserts gaps to find the optimal alignment, then computes identity as matches divided by aligned length. This handles the real-world case where homologous proteins differ in length (e.g., cow hemoglobin beta is 145 aa while human is 147 aa), a naive position-by-position comparison would show near-zero identity due to the offset, while Needleman-Wunsch correctly reports ~84%.
Task 1: Load homologous gene sequences
The first task is simple but important. It loads one of the curated gene sets or accepts custom JSON, and outputs a directory that the rest of the pipeline consumes. The `cache="auto"` decorator means switching between gene sets is instant after the first run. When you're comparing insulin, hemoglobin, and p53 in sequence, only the first load for each gene set actually executes.
Task 2: Score DNA with Carbon

Carbon-3B is a DNA language model trained on genomic sequences. It scores each species' DNA for log-likelihood, essentially a probability score for the sequence. A higher log-likelihood means the sequence looks more "natural" to the model, similar to how a language model would rate "the cat sat on the mat" higher than a random string of words. This task focuses purely on scoring, the sequence comparison comes in the next step.
The `<span><dna></span>` prefix tells the model to interpret the input as a DNA sequence rather than text. The loss is the average negative log-likelihood per token; multiplying by sequence length gives the total log-likelihood for the full sequence. Species whose DNA looks more "natural" to the model (higher log-likelihood) may have sequences closer to what Carbon saw in its training data, which skews toward well-studied model organisms like humans and mice. This is interesting in itself: the model's confidence can vary across species in ways that reflect how much genomic data exists for each organism.
Task 3: Align sequences and compute cross-species similarity

This is where the cross-species comparison happens. The task runs Needleman-Wunsch global alignment on every pair of species, at both the DNA and protein level. Pairwise means comparing every species against every other species, and identity is the percentage of matching positions in the optimal alignment. The result is two grids (one for DNA, one for protein) that show how similar each pair of species is at the molecular level, plus phylogenetic trees built from the divergence.
Comparing the two grids is where the insight comes from: if protein identity is much higher than DNA identity for a gene, that means evolution is changing the DNA but preserving the protein. That's direct evidence the protein's function is under selective pressure.
This task runs on CPU since it's pure computation, no deep learning models needed. Separating it from the GPU-bound Carbon scoring means you only pay for GPU time when you're actually running a model.
The identity matrices are the core comparison output. The gap between DNA and protein identity is the evolutionary signal: when protein identity is significantly higher than DNA identity, synonymous mutations are accumulating while the protein stays conserved. That's purifying selection in action, and it's the central biological insight this pipeline reveals.
Task 4: Fold proteins with ESMFold for 3D structure comparison

Sequence comparison tells you how much the gene has changed. Structure comparison tells you whether those changes actually matter. ESMFold predicts 3D protein structure directly from amino acid sequence, no multiple sequence alignment needed. By folding the same gene's protein from each species, we can see whether sequence divergence translates to structural divergence, or whether the fold is preserved despite the surface-level changes.
The `max_length=400` parameter is a practical constraint. ESMFold's memory usage scales quadratically with sequence length, so longer proteins can run out of GPU memory. The gene sets in this tutorial use full-length coding sequences: insulin is ~105-110 residues, hemoglobin ~145-148, and p53 ~367-393, all within the limit. If you bring your own gene set with longer proteins, you may need to increase GPU memory or raise the limit.
The PDB (Protein Data Bank) conversion function transforms ESMFold's predicted atom coordinates into standard PDB format. The Flyte report renders these as interactive 3D viewers using 3Dmol.js, so you can spin and zoom the structures directly in the Flyte dashboard.
The pLDDT (predicted Local Distance Difference Test) score is ESMFold's per-residue confidence measure. Above 90 means very high confidence, typically the structured core of the protein. Between 70-90 is confident. Between 50-70 suggests flexible loops or less certain regions. Below 50 usually indicates disorder, meaning that part of the protein doesn't adopt a fixed shape. Comparing pLDDT across species can reveal which structural regions are consistently well-folded (likely functionally important) versus which regions are more variable.
Task 5: Generate evolutionary summary

The final task brings everything together. It computes cross-species statistics, builds phylogenetic trees from both identity matrices, and produces a consolidated summary report. This task runs on CPU since it's pure computation, no models needed.
The phylogenetic trees are built from the identity matrices using UPGMA (Unweighted Pair Group Method with Arithmetic Mean) clustering. The pipeline includes a full UPGMA implementation and SVG dendrogram renderer (in the full source) that converts the pairwise distances into a tree showing which species are most closely related at the molecular level. An interesting result: DNA trees and protein trees can differ for the same gene when synonymous mutations dominate, because the DNA has diverged further than the protein it encodes.
Every run in Flyte is versioned, so you can compare results across different gene sets or different Carbon model versions and trace each result back to exactly what inputs produced it.
Running the pipeline
This will run on your flyte on your cluster which needs a GPU (recommended for ESMFold), but you can also run locally.
Default (Insulin across 6 species):
Hemoglobin comparison:
p53, including elephant:
Run Locally
The same code runs locally and remotely. Flyte handles the GPU scheduling, container builds, and data transfer between tasks. Locally you can test with the TUI for a live terminal dashboard. Remotely, the Flyte UI shows interactive reports with identity heatmaps, phylogenetic dendrograms, and spinning 3D protein structures.
What’s next
The tutorial ships with three gene sets, but the pipeline accepts custom JSON so you can compare any set of homologous genes. Grab orthologous sequences from Ensembl or NCBI Orthologs, format them as a JSON object matching the gene set structure, and pass them via `--custom_json`. There are also two companion tutorials in the workshops repo: genomic-variant-effect for mutation impact analysis and genomic-dna-generation for DNA generation with Carbon.
Check out the full code on GitHub to run on Flyte or Union and try it with your own gene sets.




