Skip to content

RAD I/O & deterministic quantification

salmon can separate the two halves of a run — mapping reads to the transcriptome and quantifying abundances from those mappings — by writing the mappings to a RAD file (the Reduced Alignment Data format shared across the COMBINE-lab tools) and quantifying from it later. The same machinery powers a fully deterministic quantification mode.

  • Map once, quantify many times — re-run quantification with different options (bias correction, --numBootstraps, gene aggregation) without re-mapping.
  • Quantify a shared RAD — quantify a RAD produced by piscem map-bulk with salmon’s EM.
  • Split the phases across machines — map on one host, quantify on another.

Add --writeRad <PATH> to any reads-mode run to also emit per-fragment mappings. The sketch or selective-alignment profile is chosen automatically from the mapping mode. Quantification still runs; add --skipQuant to map only:

Terminal window
# map only, write a RAD
salmon quant -i salmon_index -l A -1 r1.fq.gz -2 r2.fq.gz -p 16 \
--writeRad mappings.rad --skipQuant -o out_map

The file is piscem map-bulk-compatible and can be re-quantified with --rad. salmon additionally bakes an order-independent fragment-length distribution, initial abundances, the resolved library format, and — for a decoy-aware index — the decoy boundary into the RAD header, so re-quantifying it is a single pass (see Determinism below) and the requant’s quant.sf excludes the decoy block exactly as a direct run does.

Terminal window
# quantify a salmon- or piscem-produced RAD (no -i needed)
salmon quant --rad mappings.rad -l A -p 16 -o out_quant

--rad reads the RAD in parallel and runs the full EM. Reference names travel in the RAD header, so no index is required. salmon auto-detects whether the RAD came from salmon (header values are present and consumed — one pass) or from piscem (nothing baked — a first pass derives a unique-fragment fragment-length distribution, then quantifies). Bias correction (--seqBias, --gcBias, --posBias), --numBootstraps, --numGibbsSamples, and -g/--geneMap all work in RAD mode.

Fragment-length distribution (--fldPolicy)

Section titled “Fragment-length distribution (--fldPolicy)”

salmon’s RAD writer always bakes its fragment-length distribution into the header — including under --skipQuant, which suppresses only the baked abundances. On read-back that distribution takes precedence, which is what makes a requant reproduce the writing run exactly.

The consequence is that --fldMean/--fldSD/--fldMax have no effect on a salmon-produced RAD by default. They are priors, and a baked distribution replaces the prior outright rather than being blended with it. salmon warns when you supply one of these flags and a baked distribution supersedes it; a run that inherits their defaults stays quiet.

--fldPolicy chooses where the distribution comes from:

PolicyBehavior
baked (default)Use the RAD’s baked distribution when present. Exact parity with the run that wrote the file.
deriveIgnore the baked distribution; rebuild it from this RAD’s own uniquely-mapped proper pairs, seeded by --fldMean/--fldSD. This is what a piscem RAD does automatically.
priorIgnore both the baked distribution and the RAD’s fragment lengths; --fldMean/--fldSD alone determine it.

Use prior for a fragment-length sensitivity analysis — it is the only setting under which varying --fldMean changes the result:

Terminal window
for m in 150 250 350; do
salmon quant --rad mappings.rad -t transcripts.fa -l A \
--fldPolicy prior --fldMean $m --fldSD 25 -o out_fld_$m
done

aux_info/meta_info.json records which path a run took in frag_length_source (rad_baked, rad_derived, or prior), so the provenance stays auditable after the fact.

A single-end run has no fragment lengths to measure, so the distribution it bakes is simply its own --fldMean/--fldSD prior. Reading such a RAD back therefore inherits the writing run’s prior, and salmon reports frag_length_source as rad_baked_prior to distinguish this from an observed distribution. If you want your own values to apply, pass --fldPolicy prior (derive has nothing to derive from here).

A RAD produced by piscem map-bulk quantifies directly:

Terminal window
piscem map-bulk -i piscem_index -1 r1.fq.gz -2 r2.fq.gz -t 16 -o pi
salmon quant --rad pi.rad -l A -p 16 -o out_quant

Deterministic quantification (the default)

Section titled “Deterministic quantification (the default)”

RAD-mode quantification is deterministic: repeated runs at -p 1 are byte-identical, and across thread counts every value agrees to at least 10⁻⁹ — six orders of magnitude finer than the precision quant.sf reports, so the written output is stable however many threads ran. (The one thing thread count can perturb is the floating-point association order inside the EM’s parallel reduction; measured across 2–32 threads on a deliberately hard input, the largest deviation was 4.5×10⁻¹³. Everything upstream of the EM is integer-accumulated and exactly thread-count independent — see below.) Since 2.6.0 this is how salmon quantifies by default — a plain run brings the same guarantee to FASTQ input directly:

Terminal window
salmon quant -i salmon_index -l A -1 r1.fq.gz -2 r2.fq.gz -p 16 -o out

It maps the reads once to an intermediate RAD, then quantifies from it with a fixed fragment-length distribution — no second mapping pass. The intermediate RAD is written under the output directory and removed on success unless you pass --keepRad (or --writeRad <PATH> to choose its location and keep it). --deterministic is still accepted as a no-op, and meta_info.json records which path ran in inference_path.

The pre-2.6 online one-pass path remains available for one release cycle behind --online (deprecated, removal planned for 2.7.0). Its results depend on the thread count, and the online-only knobs — --forgettingFactor, --numAuxModelSamples, --numPreAuxModelSamples — warn and are ignored unless --online is passed.

The determinism comes from making the computation itself order-independent rather than sorting records:

  • equivalence-class weights accumulate in fixed-point integers (integer addition is associative, hence independent of thread count and arrival order);
  • the fragment-length distribution is built from integer count histograms in a fixed order, then frozen;
  • the bias observed models accumulate in fixed-point integers too, so the trained models — and the bias-corrected effective lengths — are byte-identical across thread counts.

--deterministic works with bias correction and does not require -t: the reference sequences for the second pass are taken from the index.

Deterministic alignment mode (-a --deterministic)

Section titled “Deterministic alignment mode (-a --deterministic)”

--deterministic also applies to alignment mode — quantifying a name-grouped transcriptome BAM:

Terminal window
salmon quant -a aln.bam -l A -p 16 --deterministic -o out

salmon writes the BAM’s placements to an intermediate RAD (baking the fixed fragment-length distribution and, with bias correction, the seed abundances) and quantifies from it in a single pass — with the same determinism guarantee as RAD mode above: byte-identical at a fixed thread count of one, and stable to well beyond the reported precision across thread counts.

By default each placement is scored by its BAM alignment score (AS), which is what --noErrorModel does in ordinary alignment mode. This is deliberate: across every benchmark with ground truth (uniform and realistic position-dependent Illumina errors, at 50 bp and 76 bp), AS scoring is at least as accurate as an error model, and it needs only a single BAM pass.

To reproduce salmon’s classic error-modeled weighting deterministically, pass --errorModel (which requires -t, the transcriptome the reads were aligned to):

Terminal window
salmon quant -a aln.bam -t txome.fa -l A -p 16 --deterministic --errorModel -o out

This trains an order-independent error model — first-order per-base transition counts accumulated as integers, merged across threads by integer addition (so the trained model is independent of thread count), then normalized once and used to score every placement. It stays fully deterministic, but costs a second BAM pass (train, then score), roughly doubling the alignment-mode runtime.

When is it worth it? On well-aligned short-read data with informative AS tags (e.g. bowtie2, STAR) it changes results but does not measurably improve accuracy against truth, so the default AS scoring is preferred. Reach for --errorModel when you specifically want parity with salmon’s traditional error-modeled quant, or when the aligner does not emit a usable AS (without which AS scoring falls back to uniform weighting).

Disk use of the --deterministic intermediate

Section titled “Disk use of the --deterministic intermediate”

The two-phase flow writes an intermediate RAD, quantifies from it, and deletes it on success. Budget roughly the size of your compressed input for it (measured ~110 MB per 1M read pairs on GENCODE v49 under the default lz4); the exact size grows with the multimapping rate, not just the read count, so no fixed per-fragment constant is reliable. Salmon warns at startup when the target volume’s free space is below the total input size, and the writer checks free space periodically during the pass so a filling disk fails within seconds — with the partial file removed — rather than silently at the end of the mapping pass.

Two knobs when space or I/O speed is a concern:

  • --radScratchDir <dir> places the intermediate on a different volume — node-local scratch, or memory-backed storage such as /dev/shm, which removes the round-trip from slow or networked filesystems entirely. The file is pid-suffixed there, so concurrent runs can share one scratch directory.
  • --radCompress zstd shrinks the intermediate a further ~30% over the lz4 default for ~11% extra wall time.

An explicit --writeRad PATH always wins over --radScratchDir, and files at user-named paths are never cleaned up by salmon — they are yours.

RAD chunks can be compressed, on by default for both --writeRad and the --deterministic intermediate:

FlagEffect
--radCompress=lz4Default. Fast LZ4; ≈ 1.25× smaller on a typical human RAD, neutral-to-faster to write.
--radCompress=zstdBetter ratio (≈ 1.9× smaller), a little slower.
--radCompress=noneUncompressed.
--noCompressRadForce uncompressed (overrides --radCompress).

Compression is transparent and lossless: chunks are decompressed by the reader, so every consumer (salmon, alevin-fry, piscem-infer) is unchanged, and a RAD with no codec tag — every file produced before this feature, and every piscem RAD — reads as uncompressed automatically. The quantified quant.sf is identical regardless of codec.