Skip to content

feat(pipeline): wire dedup --threads onto the declarative chain builder - #876

Merged
nh13 merged 2 commits into
mainfrom
nh/dedup-chain-wiring
Aug 29, 2026
Merged

nh13 merged 2 commits into
mainfrom
nh/dedup-chain-wiring

Conversation

@nh13

@nh13 nh13 commented Aug 27, 2026 •

Copy link
Copy Markdown
Member

Makes dedup --threads N a live chain path, the second command (after group) wired onto the declarative chain-builder layer. Stacked on #872 — base is nh/chain-builder and will be retargeted to main (and rebased) once #872 merges.

dedup --threads N's hand-rolled unified-pipeline construction is replaced with ChainSpec::single_stage(Stage::Dedup) → build_for → run, reusing the shared helpers #872 introduced. dedup is the closest sibling to group (same template-coordinate input, same molecule machinery), so this mirrors the group pilot (b01e18ea in #872) closely.

Key difference from the group pilot

Unlike group, dedup has no separate single-threaded fast path — its whole execute() is the unified pipeline scaled by thread count. This PR still gates on --threads and leaves the no---threads path untouched, so that path stays as the in-process parity oracle the new tests diff against. Replacing all of execute() (the one-engine cleanup) is a deliberate follow-up, not this PR.

What's here (reading order, oldest → newest)

  1. thread the duplication-ladder recorder through the chain dedup MiAssign step — the dormant chain dedup builder carried --metrics and --family-size-histogram but not --duplication-ladder; repointing dedup would have silently dropped the ladder. The ladder is order-sensitive (it samples a saturation curve at cumulative-template intervals), so it is recorded in the serial MiAssign step — the same coordinate-order seam the non-chain path uses — not the parallel serialize step. MiAssign is Serial + ByItemOrdinal, so batches arrive in input-record order and groups within a batch are coordinate-ordered, reproducing the non-chain per-group stream exactly. The finalize hook reads the recorder through the lock (not Arc::try_unwrap), holds the (path, recorder) pair in one Option so the invariant is in the type, and now also emits the "Filtered out N templates before marking" diagnostic the non-chain path emits (it must not be --metrics-only). Promotes DuplicationLadderRecorder/write_duplication_ladder to pub(crate) and adds Clone to MarkDuplicates.
  2. repoint dedup --threads onto the chain builder — the pilot: execute_chain + an early --threads dispatch placed after the reader-free pre-flight validations (which run on both paths) but before the timer/banner/reader (which add_dedup re-emits, and pre-opening would consume stdin, breaking stdin + --threads). The --no-umi override log is gated to the non-chain path so --threads doesn't double-log it. Adds the parity tests below.

Validation

The pilot adds tests that pin behaviors the non-chain oracle cannot see:

  • test_dedup_chain_matches_single_threaded — --threads 1 and --threads 4 output record-for-record identical to the non-chain path.
  • test_dedup_chain_matches_non_chain_across_knobs — the CLI-default adjacency, edit, --remove-duplicates, --no-umi, and --min-map-q (filtering the fixture's mapq-10 subfamily) all match the non-chain path, on mixed-UMI input so adjacency/edit cluster non-trivially rather than collapsing to identity; the @HD sort-order header is compared too (the @PG command-line field legitimately differs between the two invocations).
  • test_dedup_mixed_umi_fixture_distinguishes_adjacency_from_identity — a non-vacuity guard: adjacency must produce different output than identity on that fixture, else the adjacency parity cases pass vacuously.
  • test_dedup_threaded_crc_policy — --no-check-crc / --check-crc / default CRC policy honored on the chain path; the accept case asserts record identity against an intact-file baseline, not merely non-empty.
  • test_dedup_threaded_duplication_ladder_parity — the order-sensitive --duplication-ladder (plus --metrics, --family-size-histogram) file is byte-identical between the chain and non-chain paths. Runs at --threads 4 with several hundred non-uniform position groups so a reordering regression in the ladder recording actually diverges the file (a --threads 1 or uniform-size test could not catch it).

Beyond the suite (9,481 tests green), the dedup output was checked at scale against the non-chain engine on a 2.59M-record template-coordinate BAM (1.29M templates, 95% duplicate rate, 558K filtered): single-threaded and --threads 8, identity and default adjacency — output records identical (2,586,791 each, re-sorted to coordinate/mi then content-compared), and the duplication ladder, metrics, and family-size histogram byte-identical in every case; the restored filtered-templates diagnostic was confirmed emitted byte-identically on both paths at scale. The branch also went through two rounds of adversarial multi-lens review plus a scoped CodeRabbit-style pass; the most substantive finding was the dropped filtered-templates diagnostic (restored and extracted into a shared helper both paths call, so they cannot drift), and the second round's findings were all test-coverage gaps, now closed by the cases above.

Deferred / follow-ups (not in this PR)

  • One-engine cleanup — delete dedup's old unified-pipeline path entirely once every command is wired.
  • Per-command wiring PRs — the remaining dormant builders (clip, codec, correct, duplex, simplex, filter, sort, zipper, align, extract, fastq) each get their own PR.

Merge gate

Same release gate as #870/#872: the fgumi-benchmarks AWS run (fgbio equivalency + WES/WGS scale). This PR changes only the dedup --threads path's internal construction (proven record-identical at scale) and adds tests.

Risk: threaded dedup output changes, but parity and byte-identical tests pin grouping, corrected UMIs, metrics, histograms, duplication ladders, and BAM output; unsafe: none, with no allowlist update; memory, queue capacity, and thread/backpressure policy: none.

Fix: route dedup --threads N through ChainSpec while preserving the serial path.

  • Record duplication ladders in coordinate-ordered MiAssign.
  • Finalize ladders through the chain finalize hook.
  • Preserve CRC handling, diagnostics, metrics, histograms, and output behavior.
  • Add parity and non-vacuity tests for deduplication options, mixed-UMI input, mapping-quality filtering, CRC policies, and output artifacts.

@nh13
nh13 deployed to github-actions August 27, 2026 04:58 — with GitHub Actions Active
@coderabbitai

coderabbitai Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 239664ca-ecce-46c3-9706-22005571ede1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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: Pro

Run ID: 0db421c9-ca03-4308-bcb0-9e0b5e9abc69

📥 Commits

Reviewing files that changed from the base of the PR and between 211a43b and e79bc55.

📒 Files selected for processing (1)
  • src/lib/commands/dedup.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

Threaded deduplication now uses the chain pipeline after shared validation. The chain path records and finalizes duplication ladders, shares filtered-template diagnostics, and adds integration coverage for output, strategy, filtering, CRC, and auxiliary-file parity.

Changes

Threaded deduplication chain

Layer / File(s) Summary
Threaded dispatch and diagnostics
src/lib/commands/dedup.rs
MarkDuplicates is cloneable. Threaded execution uses execute_chain, while non-threaded execution keeps the existing pipeline. Filtered-template logging is shared across both paths.
Duplication-ladder chain integration
src/lib/commands/dedup.rs, src/lib/pipeline/chains/builder.rs, src/lib/pipeline/chains/commands/dedup.rs
The chain builder shares a ladder recorder with serial MI assignment. Finalization finishes the recorder and writes the configured ladder.
Threaded parity and policy validation
tests/integration/test_dedup_command.rs
Tests compare threaded and non-threaded BAM, metrics, histogram, and ladder outputs. They also cover UMI strategies, mapping-quality filtering, CRC policies, and mixed-UMI fixtures.

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

Merge Risk: ⚪ Minimal · up to e79bc

The PR wires the threaded deduplication path through the declarative chain builder while preserving the existing path and adding parity coverage; no actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant DedupCommand
  participant ChainSpec
  participant MIAssignStep
  participant DedupFinalizeHook
  DedupCommand->>ChainSpec: build threaded Dedup stage
  ChainSpec->>MIAssignStep: process groups
  MIAssignStep->>MIAssignStep: record dedup counts
  MIAssignStep->>DedupFinalizeHook: pass processed results
  DedupFinalizeHook->>DedupFinalizeHook: finish and write ladder
Loading
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title uses valid Conventional Commit syntax, has a lowercase imperative description without a period, and accurately describes wiring threaded deduplication to the chain builder.

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

@nh13

nh13 commented Aug 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@nh13

nh13 commented Aug 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai pause

@coderabbitai

coderabbitai Bot commented Aug 27, 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.

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews paused.

@codecov

codecov Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.70%. Comparing base (8a22988) to head (21a8362).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #876      +/-   ##
==========================================
+ Coverage   92.50%   92.70%   +0.19%     
==========================================
  Files         294      294              
  Lines      148546   148589      +43     
==========================================
+ Hits       137415   137746     +331     
+ Misses      11131    10843     -288     

☔ 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 force-pushed the nh/chain-builder branch from 37c42a0 to 58f0f8d Compare August 27, 2026 17:01
@nh13
nh13 force-pushed the nh/dedup-chain-wiring branch from 08a330d to 0a7b4bc Compare August 27, 2026 17:04
@nh13
nh13 deployed to github-actions August 27, 2026 17:04 — with GitHub Actions Active
@nh13

nh13 commented Aug 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

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 force-pushed the nh/chain-builder branch from 58f0f8d to d86bf32 Compare August 27, 2026 17:42
@nh13
nh13 force-pushed the nh/dedup-chain-wiring branch from 0a7b4bc to 211a43b Compare August 27, 2026 17:43
@nh13
nh13 deployed to github-actions August 27, 2026 17:43 — with GitHub Actions Active
@nh13

nh13 commented Aug 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 27, 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 force-pushed the nh/chain-builder branch from d86bf32 to 940a19d Compare August 28, 2026 00:51
@nh13
nh13 force-pushed the nh/dedup-chain-wiring branch from 211a43b to a5753d0 Compare August 28, 2026 01:47
@nh13
nh13 deployed to github-actions August 28, 2026 01:47 — with GitHub Actions Active
@nh13

nh13 commented Aug 28, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 28, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

No files to review.

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 force-pushed the nh/chain-builder branch from f49b0af to 3cbc74c Compare August 29, 2026 03:51
@nh13
nh13 force-pushed the nh/dedup-chain-wiring branch from a5753d0 to 2bf6d6f Compare August 29, 2026 03:53
@nh13
nh13 deployed to github-actions August 29, 2026 03:53 — with GitHub Actions Active
@nh13
nh13 force-pushed the nh/chain-builder branch from 3cbc74c to b67d7a3 Compare August 29, 2026 05:15
@nh13
nh13 force-pushed the nh/dedup-chain-wiring branch from 2bf6d6f to e79bc55 Compare August 29, 2026 05:16
@nh13
nh13 deployed to github-actions August 29, 2026 05:16 — with GitHub Actions Active
@nh13

nh13 commented Aug 29, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 29, 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.

Base automatically changed from nh/chain-builder to main August 29, 2026 15:32
nh13 added 2 commits August 29, 2026 08:34
…ain dedup MiAssign step

The dormant chain dedup builder carried --metrics and
--family-size-histogram but not --duplication-ladder: repointing dedup
onto the chain would have silently dropped the ladder output. Wire it so
the chain path produces a byte-identical ladder.

The ladder is order-sensitive (it samples a saturation curve at intervals
of cumulative templates), so it must be recorded in the same
serial/coordinate-order seam the non-chain path uses -- the serial
MiAssign step -- not the parallel serialize step. build_mi_assign_step
now takes an optional recorder and, after assigning MI offsets, records
each group's per-library counts in coordinate order. MiAssign is Serial +
ByItemOrdinal, so batches arrive in input-record order and groups within
a batch are coordinate-ordered, reproducing the non-chain per-group
stream exactly.

DedupFinalizeHook gains the ladder path + recorder and, in finalize,
finish()es and writes it through the lock rather than Arc::try_unwrap:
the MiAssign step's Arc clone may still be alive when finalize hooks run.

Promotes DuplicationLadderRecorder / write_duplication_ladder to
pub(crate) and adds Clone to MarkDuplicates (needed so the command struct
can populate StageOptionsBag.dedup on the live path in the follow-up).
Replace Dedup::execute's hand-rolled unified-pipeline construction for
the --threads N path with the declarative chain builder
(ChainSpec::single_stage(Stage::Dedup) -> build_for -> run). The chain
opens its own source, validates the template-coordinate sort order,
injects @pg, assigns MoleculeIds deterministically, writes the output
BAM, and writes the metrics / family-size histogram / duplication ladder
via its finalize hook -- all through the same shared helpers as the
non-chain path.

Unlike group, dedup has no separate single-threaded fast path: its whole
execute() is the unified pipeline scaled by thread count. Gate on
--threads anyway (mirroring the group pilot) and keep the no-threads path
untouched as the in-process parity oracle -- rather than replacing all of
execute(), which would remove that oracle and enlarge the diff. The
one-engine cleanup is a deliberate follow-up.

The dispatch runs after the reader-free pre-flight validations (output
collisions, strategy/min-umi combos, index-threshold, input existence),
which must hold on both paths, but before the timer/banner/reader, which
add_dedup re-emits and which would otherwise double-log and pre-consume
stdin (breaking stdin + --threads).

Three parity tests pin behaviors the non-chain oracle cannot see:
- test_dedup_chain_matches_single_threaded: --threads 1 and 4 output
  record-for-record identical to the non-chain path.
- test_dedup_threaded_crc_policy: --no-check-crc/--check-crc/default CRC
  policy honored on the chain path; accept case asserts record identity
  against an intact-file baseline, not merely non-empty.
- test_dedup_threaded_duplication_ladder_parity: the order-sensitive
  --duplication-ladder (plus --metrics, --family-size-histogram) file is
  byte-identical between the chain and non-chain paths. Runs at --threads
  4 with several hundred non-uniform position groups so a reordering
  regression in the ladder recording actually diverges the file (a
  --threads 1 or uniform-size test could not catch it).
@nh13
nh13 force-pushed the nh/dedup-chain-wiring branch from e79bc55 to 21a8362 Compare August 29, 2026 15:35
@nh13
nh13 deployed to github-actions August 29, 2026 15:35 — with GitHub Actions Active
@nh13

nh13 commented Aug 29, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 29, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

No files to review.

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 Aug 29, 2026
Merged via the queue into main with commit 07b4a51 Aug 29, 2026
17 checks passed
@nh13
nh13 deleted the nh/dedup-chain-wiring branch August 29, 2026 17:05
@nh13 nh13 mentioned this pull request Aug 29, 2026

This branch was successfully deployed

1 active deployment
github-actions — 21a8362d Deployed Aug 29, 2026 by nh13 via coverage #4005
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