Skip to content

feat(io): native CBQ input support - #213

Open
alejandrogzi wants to merge 3 commits into
scverse:mainfrom
alejandrogzi:pr/cbq
Open

feat(io): native CBQ input support#213
alejandrogzi wants to merge 3 commits into
scverse:mainfrom
alejandrogzi:pr/cbq

Conversation

@alejandrogzi

@alejandrogzi alejandrogzi commented Aug 6, 2026

Copy link
Copy Markdown

Adds single-file CBQ read input (single-end or interleaved paired-end), detected by file header rather than extension, decoded in parallel under --readFilesNthreads.

  • New src/io/cbq.rs, src/io/input.rs, src/io/reads.rs; read types/base helpers shared with the FASTQ path move to reads.
  • Decoded reads bounded by a fixed per-window record budget; peak input memory is independent of thread count and block size.
  • Quality-less CBQ emits SAM/BAM missing-quality sentinels; paired CBQ stores both mates per record (extended fields).
  • CBQ limited to ordinary alignment input (not STARsolo/SmartSeq, --readFilesCommand, or qualityless --outReadsUnmapped Fastx).

Tests: cargo test --release green (640 tests on current main base).

Benchmark

  • Core (upstream): 6af8d78e4112ac537cc2da83704911d4ce047c05
  • Fork: 93da004ebba0f8d379b761cc00b3bff4daf8c381 + uncommitted CBQ feature (incl. src/io/cbq.rs)
  • Host: AMD Ryzen 7 5700X (16 threads), 125 GB RAM, /tmp on tmpfs
  • Reference: chr1 (hg38), sparse index (--genomeSAsparseD 2, shared by both binaries)
  • Configs: core-fq (upstream), our-fq, our-cbq (CBQ input + --readFilesNthreads 4), each x SE/PE
  • Hyperfine: warmup 1; runs 8 (synthetic, real SE), runs 3 (real PE)

**our: this fork

Datasets

dataset reads source
synthetic SE 291,042 generated, pure ACGT
synthetic PE 47,420 pairs generated, pure ACGT
real SE 150,000 SRR13278444_1, chr1-only, contains N
real PE 100,000 pairs SRR13278444_1 + _2, chr1-only
  • Real reads: SRR13278444 - Mus musculus skeletal-muscle RNA-seq run (SRA metadata: "Skeletal muscle transcriptome… WT/SIRT1-KO/SIRT1-OE mice… Illumina MiSeq")

Correctness

For all 4 datasets: core-fq == our-fq == our-cbq byte-identical SAM alignment records
(header @-lines excluded; line 3 embeds the command line and always differs).

Wall time (hyperfine mean, s)

config synth SE synth PE real SE real PE
core-fq 9.291 7.988 26.231 86.531
our-fq 9.424 8.105 26.742 89.675
our-cbq 9.447 8.111 26.735 89.776

Ours is 1.4-3.8% slower than core; CBQ input adds ~0.3% over our FASTQ. All within ~2 sigma of run-to-run noise except real PE (+3.6%).

Peak RSS (/usr/bin/time -v, MB)

config synth SE synth PE real SE real PE
core-fq 5,309 3,646 9,589 6,742
our-fq 5,418 3,726 9,319 7,142
our-cbq 5,471 3,670 9,800 6,687

No systematic difference; CBQ does not inflate memory (real PE CBQ lowest of the three).

Mapping rates (written records / reads)

dataset rate
synthetic SE 291,042 / 291,042 (all map)
synthetic PE 47,420 / 47,420 pairs

Add single-file CBQ read input (single-end or interleaved paired-end),
detected by file header rather than extension, with parallel decoding
controlled by '--readFilesNthreads'. Decoded reads are bounded by a fixed
per-window record budget so peak input memory is independent of thread
count and block size. Read types and base helpers shared with the FASTQ
path move to a new 'reads' module.
The spill+merge test exercises the bounded-memory coordinate sorter, not
CBQ input; it ships with the BAM sorter change instead.
Upstream added a call to fastq::encode_base inside index_of_barcode after
the fork diverged; the move of base helpers to io::reads requires the
updated path for the CBQ-era import layout to keep compiling.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant