Skip to content

perf(decode): fold UMI-position cache into the group-key aux scan - #976

Merged
nh13 merged 1 commit into
mainfrom
nh/decode-single-pass-umi
Sep 18, 2026
Merged

nh13 merged 1 commit into
mainfrom
nh/decode-single-pass-umi

Conversation

@nh13

@nh13 nh13 commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

What

The decode step scanned each record's aux block twice on the group/dedup/consensus hot path: once in compute_group_key_from_raw (via extract_aux_string_tags) for the RG/cell/MC group key, then again in cache_umi_position (via find_string_tag_position) for the UMI value position (#334). The single-pass extractor already accepts a umi_tag and returns the UMI position, so the second walk is redundant.

This folds the UMI-position capture into the same aux scan that builds the key:

  • New compute_group_key_and_umi_from_raw / key_and_umi_for_mode pass the UMI tag into the one extract_aux_string_tags call and return the record-relative (offset, len).
  • The BAM (DecodeRecords, DecodeFromRecords) and SAM (ParseSamChunk) decode paths use these via a new apply_cached_umi helper, and fall back to the standalone cache_umi_position scan only when the key was built without reading aux data (KeyMode::None/NameHashOnly, or a name-only key path). compute_group_key_from_raw / key_for_mode are kept as thin wrappers.

Because the fused pipeline decodes once, this also trims runall's single decode, not just the standalone commands.

Tag-resolution semantics (byte-identity scope)

The UMI is now resolved by the same extract_aux_string_tags pass as RG/cell/MC, so the cached UMI is consistent with the key's own tag resolution. For spec-legal records (each aux tag present at most once, SAM §1.5) this yields the identical position the standalone find_string_tag_position scan produced. The two deliberately differ only on malformed duplicate/type-shadowed tags — the same already-accepted divergence the MC tag carries via validate_mc_tag (src/lib/grouper.rs): extract_aux_string_tags skips a non-Z entry and takes a later Z copy, whereas find_tag_position resolves the first id match and rejects it if non-Z. The relaxation makes the cached value agree with the value the grouping key actually uses.

Verification

  • Byte-identical output on well-formed input: fgumi compare bams reports Content diffs: 0 / IDENTICAL for both group and dedup over a real 60M-record benchmark BAM.
  • Existing SAM-vs-BAM decode parity, group/dedup MI-determinism (threads 1/4), and runall staged-vs-fused tests pass unchanged; new parity tests assert the folded capture equals the standalone scan on the primary, single-end, and tc-stamped secondary/supplementary paths, plus the apply_cached_umi fallback branch.
  • cargo clippy --all-targets --all-features -- -D warnings -W clippy::pedantic, cargo fmt, and rustdoc all clean.

Measurement

At t=1 on a 60M-read, 1%-error grouped BAM (Graviton4), CPU-seconds, 2 reps:

command before after Δ
group 126.1 123.6 −2.0%
dedup 139.6 137.6 −1.4%

The removed work is the per-record cache_umi_position aux walk; it is part of the shared decode path, so simplex/duplex/codec and runall's single decode benefit as well.

Risk: output changes — none for well-formed input, pinned by parity tests and existing tag-resolution behavior; unsafe changes — none, so no CLAUDE.md allowlist update is needed; memory bounds, queue capacity, and thread/backpressure policy changes — none.

Fix: capture the UMI position during the existing grouping-key auxiliary-tag scan and reuse it during BAM and SAM decoding.

  • Added and re-exported key_and_umi_for_mode.
  • Added fallback scanning when the key mode does not capture auxiliary data.
  • Preserved key-only APIs and existing grouping fallbacks.
  • Covered primary, paired, and tc secondary/supplementary records.
  • Added parity tests for standalone UMI scanning and fallback behavior.

The decode step scanned each record's aux block twice: once in
`compute_group_key_from_raw` (via `extract_aux_string_tags`) for the
RG/cell/MC group key, then again in `cache_umi_position` (via
`find_string_tag_position`) for the UMI value position (#334). The
single-pass extractor already accepts a `umi_tag` and returns the UMI
position, so the second walk is redundant on the common `KeyMode::Full`
path.

Add `compute_group_key_and_umi_from_raw` / `key_and_umi_for_mode`, which
pass the UMI tag into the same `extract_aux_string_tags` call that builds
the key and return the record-relative `(offset, len)`. The BAM and SAM
decode paths use these via a new `apply_cached_umi` helper and fall back
to a standalone `cache_umi_position` scan only when the key was built
without reading aux data (`KeyMode::None`/`NameHashOnly`, or a name-only
key path).

Because the UMI is now resolved by the same `extract_aux_string_tags`
pass as RG/cell/MC, its duplicate/mistyped-tag handling matches the
key's own tag resolution rather than the standalone scan's. For
spec-legal records (each aux tag at most once, SAM §1.5) the folded
capture yields the identical position the standalone scan produced; the
two differ only on malformed duplicate/type-shadowed tags — the same
deliberate, already-documented divergence the `MC` tag carries via
`validate_mc_tag`. Output is byte-identical on well-formed input:
`fgumi compare bams` reports 0 content diffs for both group and dedup
over a 60M-record benchmark BAM, and the SAM/BAM parity and group/dedup
MI-determinism tests pass unchanged.

Removes the per-record `cache_umi_position` aux walk (~2% of
single-thread CPU on group, ~1.4% on dedup, Graviton4) from the shared
decode path, which also serves consensus and `runall`'s single decode.
@nh13
nh13 deployed to github-actions September 18, 2026 08:50 — with GitHub Actions Active
@coderabbitai

coderabbitai Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: fae7c8e1-5166-4106-813f-01e69b6fc7c8

📥 Commits

Reviewing files that changed from the base of the PR and between d23da3d and 1641e0a.

📒 Files selected for processing (4)
  • crates/fgumi-bam-io/src/grouping.rs
  • crates/fgumi-bam-io/src/lib.rs
  • src/lib/pipeline/steps/parse/decode.rs
  • src/lib/pipeline/steps/parse/sam.rs

Included review availability: 0 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.


Walkthrough

The grouping API now returns an optional record-relative UMI position with each group key. Decode and SAM parsing paths reuse that position and scan only when capture is unavailable.

Changes

Inline UMI Capture

Layer / File(s) Summary
Grouping capture API
crates/fgumi-bam-io/src/grouping.rs, crates/fgumi-bam-io/src/lib.rs
key_and_umi_for_mode returns a GroupKey and optional UMI position. Full-key paths capture positions during auxiliary-tag scanning, while other modes return no capture. Bounds and conversion checks protect the returned offset and length. Tests cover primary and tc secondary/supplementary records.
Decoded-record UMI caching
src/lib/pipeline/steps/parse/decode.rs
DecodeRecords and DecodeFromRecords use the combined key-and-UMI operation. apply_cached_umi uses the captured position or falls back to scanning when a UMI tag is configured.
SAM parsing integration
src/lib/pipeline/steps/parse/sam.rs
SAM parsing applies the captured UMI position from the combined grouping operation and removes the separate cache scan.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Refactor

Sequence Diagram(s)

sequenceDiagram
  participant Parser
  participant key_and_umi_for_mode
  participant apply_cached_umi
  participant DecodedRecord
  Parser->>key_and_umi_for_mode: compute GroupKey and optional UMI position
  key_and_umi_for_mode-->>Parser: return GroupKey and UMI position
  Parser->>apply_cached_umi: apply position or fallback scan
  apply_cached_umi->>DecodedRecord: cache UMI bytes
Loading

Merge Risk: ⚪ Minimal · up to 1641e

The optimized UMI caching preserves the record-byte offsets consumed by parsing, with no current merge-blocking risk identified.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title uses valid Conventional Commit format with the perf type and decode scope. The lowercase imperative description accurately identifies the UMI-position cache optimization and has no endin…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@nh13

nh13 commented Sep 18, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai pause

@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews paused.

@codecov

codecov Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.18699% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 96.09%. Comparing base (d23da3d) to head (1641e0a).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
crates/fgumi-bam-io/src/grouping.rs 98.79% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #976      +/-   ##
==========================================
- Coverage   96.12%   96.09%   -0.03%     
==========================================
  Files         293      293              
  Lines      146660   146751      +91     
==========================================
+ Hits       140975   141022      +47     
- Misses       5685     5729      +44     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nh13

nh13 commented Sep 18, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13
nh13 added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit fb2c977 Sep 18, 2026
17 checks passed
@nh13
nh13 deleted the nh/decode-single-pass-umi branch September 18, 2026 20:19
@nh13 nh13 mentioned this pull request Sep 18, 2026

This branch was successfully deployed

1 active deployment
github-actions — 1641e0ad Deployed Sep 18, 2026 by nh13 via coverage #4572
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