Skip to content

Faster DP, an optional convex gap, and a guide-tree fix; includes #1 - #2

Merged
glennhickey merged 5 commits into
cactus-integrationfrom
ai-optimization
Oct 2, 2026
Merged

glennhickey merged 5 commits into
cactus-integrationfrom
ai-optimization

Conversation

@glennhickey

Copy link
Copy Markdown

Summary

Makes minipoa a faster and, with the new options, as-accurate alternative to abPOA as cactus bar's base aligner. Used by ComparativeGenomicsToolkit/cactus#2026. This includes #1, merged in and ported to the new convex traceback, so merging this should mark #1 as merged too.

cactus bar, 8 Mb human/mouse, 8 threads time memory
before 29 s 3.1 GB
after, same settings and identical output 9 s 2.8 GB
after, cactus's new defaults (convex gap + guide tree) 12 s 4 GB
abPOA, for reference 51 s 5.5 GB

Evolver mammals (mafComparator), with cactus's new <minipoa> defaults: 0.846 / 0.646 → 0.854 / 0.652 (abPOA 0.853 / 0.652).

Speed, with identical alignments (0416daa, 89987e2)

  • The horizontal-gap recurrence ran one column at a time and took 63% of poa(). It is now a vectorised decaying prefix-max scan, fused with the M/D pass, with a fast path for single-predecessor rows.
  • DP scratch stays on the parameter handle between minipoa_msa() calls. A fresh buffer per call faulted the whole matrix in each time: 49 M page faults on the human/mouse run.
  • The static band is centred on the edge-weighted mean position of the sequences through each node. The heaviest path's length strings together every sequence's insertions, which widened every row.
  • The DP is memory-bound: at 8 threads it ran no faster than at 2. To cut the traffic:
    • Vertical gap states are stored as 16-bit saturating distances below M, which is exact. Open + extension penalties of 32768 or more are now rejected.
    • Horizontal gap states are stored as two bits per cell: "is the max" and "opened here".
    • Rows go to the matrices with non-temporal stores, while successors read a cache-resident ring of recent rows.

Accuracy (opt-in; cactus turns both on)

  • An optional second gap piece makes the gap cost convex, as abPOA's is: minipoa_set_gap2(), and --gap_open2/--gap_ext2 on the command line. This adds a convex traceback.
  • The guide tree skipped every minimizer found in more than a quarter of the sequences. Below 8 sequences that left every similarity at 0, so progressive mode kept the input order. Minimizers found in 16 or fewer sequences are now always used.

Robustness

  • Fix traceback at the end row: "backtrack found no predecessor" #1's end-row fix (ofirr) is in both tracebacks. The traceback starts from every state of the end cell, not M alone, and a real query base scored against the end row's virtual N is emitted as an insertion.
  • When the traceback still finds no path through the band, poa() retries that query unbanded instead of failing.
  • mg_chain_backtrack() no longer reads t[-1] for an anchor with no predecessor.

Testing

  • Identical output to the previous version: on simulated truth sets, on cactus bar's human/mouse output, and through cactus's evolver tests. SSE2, SSE4.1, AVX2 and emulated AVX-512 builds agree. ASan/UBSan are clean. An independent review checked the DP cell by cell against a scalar reference.
  • The Fix traceback at the end row: "backtrack found no predecessor" #1 merge was compared against the previous commit on 203 runs: 29 datasets × 7 parameter sets, including bands down to 10 and seeding. All are identical.
  • Fix traceback at the end row: "backtrack found no predecessor" #1's reproducer now aligns in its band with both gap models. With the single gap, the output is byte-identical to Fix traceback at the end row: "backtrack found no predecessor" #1's.
  • Windows shaped like Fix traceback at the end row: "backtrack found no predecessor" #1's: a sequence, a short exact prefix of it, then an unrelated one. There were 150, in input order, since bar's windows are not always longest first. The previous version needed the unbanded retry on 10, 16 and 9 of them (single gap; narrow band; convex gap); this version needs it on none.
  • Random prefix, suffix and internal fragments, in input order: 1072 of 1080 runs are identical to before. The other 8 are windows the previous version could align only through the retry.
  • Every one of those MSAs was checked to spell out its input sequences.
  • ASan/UBSan on the merged code are clean: a 200-run matrix (both gap models, bands down to 5, seeding, guide tree), plus the 150 Fix traceback at the end row: "backtrack found no predecessor" #1-shaped windows across four parameter sets.
  • The CMake build gives the same 66 warnings as cactus-integration, and no new ones.

cactus builds minipoa only on x86 for now (it is off on ARM), so the ARM path is untested.

Please merge with a merge commit rather than squash/rebase, so the commits cactus pins stay reachable.

🤖 Generated with Claude Code

ofirr and others added 5 commits September 28, 2026 10:34
poa() started the traceback at the end-row cell with op = M_OP, but the
forward DP sets that cell to max(M, D, I).  When the band of every
predecessor of the end node stops short of the last query column (e.g. a
short branch straight into the sink), the optimum reaches the last column
through insertions in the end row itself, M == I there, and the traceback
threw "backtrack found no predecessor".  Start with ALL_OP, like every other
cell.

Walking the end row then exposed a second problem: a real query base scored
against the end row's virtual N was emitted as a (mis)match to the end node,
which corrupted the graph in add_path (double free).  Emit it as an
insertion instead.

Reproducer: 3 sequences 5051/17/1352 bp, the 17 bp one a prefix of the
first, -O -600 -E -4 -b 1000 -f 10 (cactus bar settings).  Output is
byte-identical to before on every input that did not crash.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Speed, with alignments unchanged:
- The horizontal gap recurrence, which ran one column at a time and took
  63% of poa(), is now a vectorised decaying prefix-max scan, fused with
  the M/D pass, with a fast path for single-predecessor rows.
- DP scratch is kept on the parameter handle between minipoa_msa() calls.
  A fresh buffer per call faulted its whole matrix in every time: 49M page
  faults on 8Mb of human/mouse through cactus bar, and more system time
  than alignment time.
- The static band is centred on the edge-weighted mean position of the
  sequences through each node, not the heaviest path's length, which
  strings together every sequence's insertions and widened every row.
- Gap states are stored as 16-bit saturating distances below M (exact;
  open + extension penalties of 32768 or more are now rejected), and rows
  are streamed to the matrices with non-temporal stores while successors
  read a cache-resident ring of recent rows.  The DP is memory-bound: at 8
  threads it ran no faster than at 2.
- When the traceback finds no path through the band, poa() aligns that
  query again without one instead of failing.
On 8Mb of human/mouse at 8 threads, cactus bar went from 29s and 3.1GB to
10s and 3.5GB, with identical output.

Accuracy (opt-in):
- An optional second gap piece makes the gap cost convex, as abPOA's is:
  minipoa_set_gap2(), and --gap_open2/--gap_ext2 on the command line.
- The guide tree skipped every minimizer found in more than a quarter of
  the sequences, so below 8 sequences every similarity was 0 and
  progressive mode kept the input order.  Minimizers in 16 or fewer
  sequences are now always used.
With both, and abPOA's gaps (400/30 and 1200/1), cactus's evolver mammals
test goes from 0.846/0.646 to 0.854/0.652 (abPOA 0.853/0.652).

Fixes:
- mg_chain_backtrack() read t[-1] for an anchor with no predecessor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The traceback asks only two things of I (and I2): is it the cell's score,
and did it open at this column rather than extend from the one before.  It
is always one or the other, being the larger of the two, so the DP now
keeps those two bits, computed from the exact values, in place of a 16-bit
value: 8 bytes a cell become 6.25, and 12 become 8.5 with a convex gap.
The "opened" bit comes out of the scan a column early and is stored that
way, so it costs the DP no shuffles.  Alignments are unchanged.

On 8Mb of human/mouse at 8 threads, cactus bar goes from 10s and 3.5GB to
9s and 2.8GB, and with the convex gap from 14s and 5.6GB to 12s and 4GB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The convex traceback had the two problems #1 fixes in the single-piece
one.  It started from M alone, although the end cell holds the max of
all five states, so when the band let the optimum reach the last column
only along the end row it found no predecessor.  And had it walked the
end row, it would have emitted a real query base scored against the end
row's virtual N as a match to the end node.  Start from ALL2_OP, and
emit such a base as an insertion.

The unbanded retry had been covering for this: #1's window aligned, but
without its band.  On inputs shaped like it (a sequence, a short exact
prefix of it, then an unrelated one; 150 of them, in input order) the
convex traceback now never needs the retry, against 9 before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes "backtrack found no predecessor" in the single-piece traceback;
the previous commit does the same for the convex one.  #1's window now
aligns in its band, to #1's exact output, with either gap model.

Everywhere else the output is unchanged.  203 runs over the simulated
sets (seven parameter sets, bands down to 10, seeding) are identical to
the previous version, and so are 1072 of 1080 over random prefix, suffix
and internal fragments taken in input order.  The other 8 are windows
the previous version could only align through the unbanded retry; this
aligns them in their band.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants