← Back
Home › Systems › Biotech and life sciences

genome-pipeline

genome alignment and variant calling

BWA, GATK

Open the genome-pipeline pagegenome-pipeline Burrows-Wheeler transform (BWT)compression: Reversible permutation that sorts every character by the context following it, clustering equal bytes into runs; the core of bzip2 and of FM-index text search.›Seekable compressed streamcompression: Independently compressed blocks plus an index for random access (BGZF, seekable zstd, eStargz)Interval treedata-structures: Index of ranges that answers "which intervals overlap this point or range?" in O(log n + k); static sorted-array form is the compact, cache-friendly choice.›Rank/select bitvectorsdata-structures: Bitvector plus small count tables: ones before position i and position of the k-th one in O(1); Elias-Fano builds compressed sorted lists on it.›Swiss tabledata-structures: Open-addressing hash table that filters 8-16 slots at once with 1-byte hash tags and SIMD, giving fast lookups at 87.5 % load.›SIMD programminghardware-os: Process 4-16 lanes per instruction with AVX2, AVX-512 or NEON via Rust std::arch, with runtime dispatch and a portable fallback.›Bayesian genotype likelihoodssearch-sort: Per-site genotype likelihoods from read base qualities, combined with priors to call variantsDe bruijn graph assemblysearch-sort: Assemble reads by walking a k-mer de Bruijn graph; tip and bubble removalExternal merge sortsearch-sort: Sort data larger than RAM: write memory-sized sorted runs, then k-way merge them with large sequential buffers; terabytes in one merge pass.›Fm indexsearch-sort: BWT plus rank structures for backward search: count and locate substrings in compressed spaceMinimizer kmer indexsearch-sort: Index sequences by window minimizers of k-mers for fast seeding and chaining (minimap2)Pair hmmsearch-sort: Pair hidden Markov model for read-haplotype likelihoods, vectorized forward algorithmSmith-Waterman local alignmentsearch-sort: Dynamic programming that finds the highest-scoring pair of similar substrings of two sequences under a substitution score and affine gap penalties, in O(nm) time.›Suffix arraysearch-sort: Sorted array of all suffix start positions of a text; finds every occurrence of any substring by binary search, at 4-5 bytes per text byte.›Huffman codingcompression: Optimal prefix code with whole-bit codeword lengths; table-driven decoding at about one symbol per few cycles, the entropy stage of DEFLATE, JPEG and zstd literals.›Run length encodingcompression: Run-length encoding of repeated values, often combined with delta and bit packingVcf variant normalizationsearch-sort: Parse VCF and left-align/trim indels so equal variants share one representationBloom filterdata-structures: Bit array answering "definitely not present" or "probably present" in about 10 bits per key for 1 % false positives; inserts are cheap, deletes impossible.›BFS and DFSgraphs: The two basic graph traversals; BFS gives hop distances and levels, DFS gives discovery/finish order for cycles, components and topological order.›Buffer pools forzero-allocation I/Ohardware-os: Preallocate a fixed set of aligned I/O buffers and recycle them through a free list, so the hot path never mallocs, faults or unmaps. The bounded count doubles as back pressure.›K way mergesearch-sort: Merge k sorted streams with a heap or loser tree (merging iterators)Pattern-defeating quicksort(pdqsort)search-sort: Introsort successor: quicksort with adaptive pivots, linear on sorted and equal-heavy input, heapsort fallback; the unstable sort of Go and (until 1.81) Rust.›Rolling hashdata-structures: Rabin-Karp polynomial rolling hashes and winnowing for substring fingerprints and document matchingFloating-point errornumerics: How IEEE 754 rounding error accumulates and how to contain it: compensated (Kahan/Neumaier) and pairwise summation, error-free transformations with FMA, ULP comparisons.›Fast hash functionsdata-structures: Non-cryptographic hashes built from 64×64→128-bit multiplies and xor-shifts; pick by key size, trust level and whether the hash is persisted.›Edit distancesearch-sort: Levenshtein/Damerau distance by DP, bit-parallel Myers variant+ 1 moreSparse matrices (CSR)numerics: Compressed sparse row stores only nonzeros as row pointers, column indices and values; SpMV streams them once, so it is memory-bandwidth bound.›Memory orderingconcurrency: What Relaxed, Acquire, Release and SeqCst atomics actually guarantee, what they cost on x86 and ARM, and which one each pattern needs.›fsync, O_DSYNC and O_DIRECThardware-os: What fsync, fdatasync, O_DSYNC, O_DIRECT and F_FULLFSYNC really guarantee on Linux and macOS, and the exact call sequences for crash-safe files.›

How it is built

Architecture overview

A genome pipeline turns the raw output of a sequencer, hundreds of millions of short text reads of a person's DNA, into a list of the places where that person differs from a reference genome. It does this in four stages: align each read to the reference, organise the alignments (sort, index, mark duplicates), call variants by comparing the reads piled up at each locus, and write the result as a compact, indexed file format. The idea that makes it tractable is seed and extend: never compare a read to the whole genome, find a few exact short matches through an index, and run an expensive alignment only around them. The same separation applies downstream, where the expensive probabilistic model runs only on regions where the reads disagree with the reference. BWA and minimap2 are the aligners, samtools and htslib the file layer, and GATK the best-known caller.

Parts:

 FASTQ reads ─► seed: index lookup ─► chain / extend ─► SAM records (one per read)
                (FM-index | minimizers)  (Smith-Waterman, SIMD)       │
                                                                       ▼
        sort by coordinate ◄── BGZF-compressed BAM chunks ◄── write (threads, buffers)
        (external merge sort)                  │
              │                                ▼
              ├─► mark duplicates ─► analysis-ready BAM + index (.bai/.csi)
              ▼
   per interval (scatter): pileup ─► active regions ─► local assembly ─► pair-HMM ─► genotype likelihoods
              │
              ▼
   GVCF per sample ─► joint genotyping across samples ─► VCF (normalised, indexed) ─► filter / annotate

A whole genome is processed by scatter: the reference is cut into intervals, each interval is a job, and the results are gathered. The design has two throughput regimes: the aligner is CPU and memory-latency bound on one machine, the rest is I/O and sorting bound across files.

How the parts fit together

Reference index: two designs

The aligner needs an index of the reference that answers "where does this 20-mer occur?". Two designs compete, and they are alternatives:

  • FM-index (fm-index, no page yet): a Burrows-Wheeler transform of the reference plus rank structures supports backward search, counting and locating a substring in compressed space, and the Suffix array (sampled) turns a match range into positions. BWA builds the FM-index of the reference with its index command (source: BWA README). It finds exact matches of any length, which suits short, accurate reads and keeps the index small for a human-sized genome.
  • Minimizer index (minimizer-kmer-index, no page yet): from each window of w consecutive k-mers keep only the smallest by hash, store the kept k-mers in a hash table (Swiss table with a fast hash; rolling-hash, no page yet, is the cheap way to hash overlapping k-mers). Minimap2 uses this: it extracts (k,w)-minimizers of the reference and indexes them in a hash table, ignores the most frequent minimizers (the -f option, 2e-4 by default), and reads reference bases in batches of 4G by default (source: minimap2 README, algorithm overview). A table lookup per minimizer is faster than a backward search, and sampling makes seeds fewer, so it scales to long, noisy reads (PacBio, Nanopore) where exact long matches are rare. The BWA README states that BWA-MEM has been replaced by minimap2 for long reads. The contract with the aligner is the same in both cases: a query k-mer in, a list of reference positions out, with repeat-heavy seeds suppressed.

Aligner

Seeds are not alignments. Both aligners collect seeds, then chain them: minimap2 sorts seeds by reference position and chains them with dynamic programming, and each chain is a candidate mapping (source: minimap2 README). Only then does the expensive step run, base-level alignment of the gaps between seeds with Smith-Waterman alignment (affine gaps, O(nm)), usually banded and vectorised with SIMD programming: many cells or many reads per instruction. Bit-parallel edit distance (edit-distance, no page yet) serves where only the number of differences matters. Minimap2 splits chains where the accumulated score drops by -z (default 400) and filters seeds likely to make both a long insertion and a long deletion. The output is one SAM record per alignment (source: SAM spec) with the position, a mapping quality, a CIGAR string for the base-level path and flags for pairing, strand and primary or secondary status.

Alignment files

A SAM line has eleven mandatory fields (name, flag, reference, position, mapping quality, CIGAR, mate fields, sequence, quality). The binary form BAM is compressed in BGZF, a gzip variant made of independent blocks, each at most 64 KiB before and after compression (source: SAM spec, section 4.1). Independent blocks give random access: an index stores virtual offsets, an unsigned 64-bit value of block start shifted left 16 bits plus the offset inside the block (coffset<<16 | uoffset; source: SAM spec). This is the seekable-compressed-stream block (no page yet); Huffman coding is the entropy stage of the DEFLATE inside each block. CRAM is a further format for the same data. The htslib README describes the C library that reads SAM, CRAM and VCF and is the core of samtools and bcftools; it implements a generalised index with extension .csi and ships the tabix indexer and the bgzip utility (source: htslib README). Queries "all reads overlapping chr7:1,000,000-1,001,000" go through a binning or tree index; Interval tree is the in-memory version of the same idea, used for annotation and interval overlap. The RLE and bit-packing blocks fit qualities, which are long runs of similar small integers (run-length-encoding, no page yet).

Sort and merge

Callers need reads in coordinate order so that all reads over a locus are adjacent; the SAM header records this as SO:coordinate (source: SAM spec). Sorting a BAM bigger than memory is External merge sort: sort memory-sized chunks (pdqsort, or Radix sort on the integer coordinate key), spill each as a compressed temporary file, then k-way merge (k-way-merge, no page yet) them. Buffer pools keep the hot path from allocating, and the I/O rules in fsync and direct I/O decide how temporary files are written. Radix beats comparison sort when the key is a small integer; pdqsort is the safe default for composite keys such as (reference, position, strand). Duplicate marking works on the sorted file because duplicate fragments from the same molecule land at the same coordinates (flag 0x400 marks them; source: SAM spec).

Local assembly and likelihoods

Single-nucleotide differences are easy to find by counting. Insertions, deletions and clustered changes are not, because aligners place them inconsistently. A caller like GATK's HaplotypeCaller therefore finds active regions where reads disagree with the reference, reassembles the reads of that region on a graph of k-mers (de-bruijn-graph-assembly, no page yet: a Bloom filter or hash table counts k-mers, BFS and DFS walk paths; tips and bubbles are pruned), and extracts candidate haplotypes. Each read is scored against each haplotype with a pair hidden Markov model (pair-hmm, no page yet), a forward algorithm over match, insertion and deletion states vectorised with SIMD. Those read-haplotype likelihoods feed bayesian-genotype-likelihoods (no page yet): combined with a prior they give a likelihood for every genotype at a site. Products of many small probabilities underflow, so the arithmetic runs in log space or with scaling, which is the point of Floating-point error. The contract between these stages is a list of candidate alleles per site and a table of log-likelihoods (read by haplotype).

Variant files and joint calling

Output is VCF. Because the same indel can be written in several equivalent ways, vcf-variant-normalization (no page yet) left-aligns and trims indels so equal variants have one form, which is required before comparing or merging callsets. GATK's germline workflow runs HaplotypeCaller in GVCF mode on a single sample, scattered over an interval list, and produces one GVCF per sample; several GVCFs go to GenomicsDBImport and then to joint genotyping, producing a multi-sample VCF (source: gatk-workflows gatk4-germline-snps-indels README). The benefit of GVCFs is that adding a sample does not require re-reading every other sample's reads. A site-by-sample table is mostly "no variant", so Sparse matrices (CSR) are the natural layout (GenomicsDB's internal format may differ).

Parallel runtime

Threads in an aligner share the index read-only, so the cost is memory latency, not locking; the places that need care are the work queue and the ordered output writer, where Memory ordering decides how a producer hands a batch to a consumer. Across machines the unit is the genome interval and the sample, as in the scatter above.

Data and control flow

One sample, reads to variants:

  1. FASTQ reads in; an index is already built once for the reference (bwa index, or minimap2 on the fly or a saved .mmi).
  2. Each worker takes a batch of reads, looks up seeds, chains and extends; minimap2 reads query bases in batches of 200M by default (the -K option; source: minimap2 README).
  3. Alignments are written as SAM records, compressed to BGZF blocks, and spilled to disk in sorted chunks.
  4. Chunks are merged into a coordinate-sorted BAM; an index is written; duplicates are marked.
  5. The genome is cut into intervals; per interval, the caller scans for active regions, assembles, runs the pair-HMM and writes GVCF records.
  6. GVCFs are merged per interval across samples and jointly genotyped; the VCF is normalised and indexed. Time goes to the aligner's extension step and to the pair-HMM (both vectorised dynamic programming), and to I/O during sorting.

One lookup in the index (seed step):

  1. Take a k-mer or minimizer from the read.
  2. FM-index: backward search narrows a suffix-array interval character by character; minimizers: one hash lookup.
  3. Convert the interval or bucket to reference positions; drop seeds from over-frequent k-mers.
  4. Hand the positions to chaining.

Scaling

  1. Alignment throughput is bound by memory latency of index lookups and the extension DP. Levers: more threads on one node, SIMD extension, cache-friendlier index layouts (BWA-MEM2 is a reimplementation whose output is meant to match BWA-MEM 0.7.17; source: its README), then more nodes with sample-level parallelism.
  2. Sorting and file I/O are next: big temporary files, many small writes. Levers: larger sort buffers, more compression threads, fast local scratch, buffer pools, fewer intermediate files (stream aligner output into the sorter).
  3. Variant calling parallelises across intervals nearly perfectly; the pair-HMM is the hot loop. Levers: SIMD or GPU/FPGA kernels for the HMM, tighter active regions, scatter into more intervals.
  4. Cohort size: joint calling touches every sample at every site. The lever is the GVCF design plus a database import step, then splitting by interval. Order of: a human genome is about 3 billion bases, and 30x coverage means on the order of 100 billion sequenced bases, so every constant factor matters.

Failure modes

FailureSymptomCauseFix
Wrong reference or contig namesno reads map, or contigs missingchr1 vs 1 naming, wrong buildindex and callers use one reference file; check the header
Unsorted or wrongly sorted BAMcaller errors, index build failsSO tag missing, sorted by name not coordinatesort by coordinate; check @HD SO:coordinate
Repeat explosionaligner very slow in some regionsrepeat seeds with millions of hitscap seed frequency (minimap2 -f), mask low-complexity
Mapping biasreference allele favoured, indels misplacedscoring prefers mismatches to gapslocal reassembly, tuned scoring
Duplicate inflationfalse confident callsPCR duplicates counted as independent evidencemark duplicates before calling
Underflow in likelihoodsNaN or zero probabilitiesproducts of small probabilities in linear spacelog space, scaling
Equivalent indels disagreefalse differences between callsetsdifferent left-alignmentnormalise VCFs before comparing
Temporary disk fullsort fails latemany chunks of a large BAMpre-size scratch, limit sort memory, stream
Truncated BGZF file"unexpected EOF"job killed while writing, missing end-of-file marker blockwrite to temp name and rename on success

Real implementations

namewhat it isrepolicencenotes
BWA (BWA-MEM)short-read aligner on an FM-indexlh3/bwaGPL-3.0 (COPYING)paper; README points to minimap2 for long reads
BWA-MEM2reimplementation of BWA-MEM for speedbwa-mem2/bwa-mem2MIT (LICENSE file)output meant to match BWA-MEM 0.7.17 (README)
minimap2aligner on a minimizer index with chaining; short reads, long reads, spliced, assemblylh3/minimap2MITpaper
samtools / htslibfile tools (sort, index, view) and the C library behind SAM, BAM, CRAM, VCFsamtools/samtools, samtools/htslibMIT/Expatspecs in hts-specs
GATKvariant discovery toolkit: HaplotypeCaller, joint genotypingbroadinstitute/gatkApache-2.0germline workflows in gatk-workflows

The alternative: a reference-free route assembles reads into contigs (de Bruijn graph or overlap graph) and compares assemblies, which finds large structural differences and novel sequence but costs more compute and memory than mapping. Pick mapping-plus-calling for resequencing one individual against a good reference; assemble when no reference exists.

Sources

Similar systems

Share the most building blocks with this one.