Repository navigation
Pick the DP score width from bounds on the scores; check it, retry at 32 bits - #14
Merged
Merged
Conversation
… bits The 16-bit gate budgeted the DP's range as if "minus infinity" sat at INT16_MIN + max(mismatch, oe1, oe2), but add820b (yangao07#79) put inf_min 512*max(e1,e2) higher without taking that out of the budget. With large scores the floor reached real scores: a last-train model scaled for cactus (match <= 210, mismatch >= -1020, -O 1290,3870 -E 60,2) put it at +1824, floor values won maxes, and the backtrack aborted with "Error in cg_backtrack" or, when it did not, could retrace a wrong alignment. On 150 short windows with that model 36110ac aborted on 81 and gave a different MSA from the 32-bit DP on 9. simd_abpoa_score_range() now works out, per width and in 64 bits, how far floor-derived values can move from the floor (down: mismatches along a diagonal, a gap open, one SIMD_SET_F step; up, when the band is narrower than the query: matches along a diagonal of unreachable cells, which stay in the band's first SIMD block), puts the floor just high enough that nothing wraps, and uses 16 bits only if every real score stays above the zone where a floor-derived value could reach it. When every row covers the whole query that is proven (no real H is below -(g(D) + g(qlen)): delete down to the row, insert across); with a narrower band it is an estimate. 32 bits uses bounds that need no band geometry. This holds for every gap and alignment mode and any non-negative scores; max_mat/min_mis are recomputed from the matrix. Checks: the backtrack flags a failure instead of aborting; the best score must be in range; and an alignment whose width is only estimated safe (16 bits with a band narrower than the query, or 32 bits on graphs deeper than about 1.5M rows for a 10 kb query with trained scores) has every stored H checked, three SIMD ops per block, for a value in that zone or above qlen*max_match. A run that passes is the exact DP. If a check fails, a 16-bit alignment is redone in 32 bits after restoring the result and the band bounds (node_id_to_max_pos_left/right, through an undo log) it changed; a 32-bit one aborts with a message naming the scores and sizes. No 64-bit path: for trained scores 32 bits is proven up to graphs whose banded DP would need tens of GB. Public API unchanged. -DABPOA_TEST_SCORE_RANGE (tests only) lets the environment force 32 bits or a losing 16-bit floor. Output is unchanged wherever 36110ac was right: byte-identical MSAs with cactus's default and trained (E1=30) scores and abPOA's defaults, and identical to a forced 32-bit DP for SSE2, SSE4.1, AVX2 and dispatch builds and for 16-bit floors forced high enough to lose (all caught and redone). The gate admits more alignments to 16 bits than before (two gaps at the cheaper piece, not one at e1), all of them proven. tests/run_all.sh: test_data/seq.fa with those scores must equal the same run with every score halved, under three band settings (36110ac aborts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013YWGkqE3ND862JF8QRZERV
tests/test_score_range.sh builds abPOA with -DABPOA_TEST_SCORE_RANGE and checks that 16- and 32-bit DP agree in every gap and alignment mode, banded and not; that a 16-bit floor forced to lose is caught and redone at 32 bits; that both sides of the 16-bit query-length and graph-depth limits give the 32-bit result; and that a trained model with gap extension 60 gives the same result as with every score halved. run_all.sh picks it up with the other test_*.sh scripts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013YWGkqE3ND862JF8QRZERV
The zone scan for widths that are not proven safe ran inside the DP row loop. It never runs for cactus's alignments, but its code alone made GCC compile the hot loop differently: on a window that runs entirely at 32 bits, f46f6c6 took about 3-4% more cycles than 36110ac. The scan now runs once, after the DP, in a separate non-inlined function per width. Every row's band is still in memory for the backtrack, so it reads the same stored H values the in-loop scan saw: row 0 and every row computed, skipping rows outside the subgraph and, in extend mode, rows after a z-drop stop. On pinned, simultaneous old/new pairs the cycle count is back to 36110ac's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013YWGkqE3ND862JF8QRZERV
Outside local mode, row 0 holds a gap along the query on each gap piece, -(o_k + e_k*i). The width choice bounds only the cheaper piece (the real score), so with convex gaps whose first extension exceeds the largest match score, the other piece left the 16-bit range in a run counted as proven. The best-score check happened to catch it and redo the alignment at 32 bits (-M 1 -X 4 -O 4,24 -E 100,1 -b -1 with a 400 bp query), but nothing guaranteed that. At 32 bits the same products could overflow int. Each piece is now computed in 64 bits and clamped at inf_min. Row 0's F values are not used after its H is set (the next row recomputes F and the backtrack stops at row 0), and H, the cheaper piece, is above the floor whenever the run is valid, so output does not change for any input the old code handled correctly. Adding a per-piece term to the width test instead would send these alignments to 32 bits for nothing. tests/test_score_range.sh: that input must give the 32-bit result at 16 bits without a retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013YWGkqE3ND862JF8QRZERV
The score-range error messages printed abpt->max_mat and min_mis, which the width rule does not trust: a library caller that sets its own matrix may leave them stale. Print the M and X that simd_abpoa_score_range() recomputes from the matrix instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013YWGkqE3ND862JF8QRZERV
…sing them When even the 32-bit bounds did not fit (qlen*(M+X) near 2^31, or qlen*M itself beyond 2^31), abPOA stopped before aligning with "scores too large". A fuzz campaign found 28 such inputs that 36110ac aligned correctly. Now a 32-bit alignment the bounds do not prove is run with every check on, and abPOA stops only if a check fails: - The floor sits as low as the band's first SIMD block allows (pn diagonal steps for floor-derived values, not qlen+1), leaving real scores the most room; floor-derived values that go further fail the checks rather than go unseen. This also applies to 32-bit runs the old rule checked with the qlen+1 allowance, where real scores could reach that higher zone although the result was right (fuzz case 1129). - The scan also requires every stored H to be at least low, the floor for a full band: from there no later step can wrap below INT_MIN. With that, a wrap no longer needs qlen*M + step <= INT_MAX to be seen, so where qlen*M does not fit, the highest H accepted is INT_MAX - M, from which no step can wrap. - Only scores whose checks cannot fit in 32 bits at all are refused up front: 3X + M + 3OE + pn/2*EM + 5PS >= 2^31, in 64 bits. Inputs the bounds do prove are unchanged: same width, floor and output. Of the 28 fuzz inputs (and 1129), 18 now give the correct output (17 equal 36110ac, confirmed by its forced-32-bit run and, where scaled, by the unscaled scores; on 1559, 36110ac's row 0 overflowed int and the new output equals the unscaled one). Five fail a check, all with real scores beyond 32 bits (local best near 3.5e9 and 1e10, in-band scores to -3.7e9 and -7.5e9, H to 1.6e10); on one of them 36110ac's output was still right. Six are refused: a gap open of 0.68e9 to 1.7e9, a mismatch of 0.72e9, or input scores beyond 32 bits. tests/test_score_range.sh: cactus's default scores times 10000 on a banded 1.2 kb window must give the result of the scores themselves, and a mismatch of 8e8 must be refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013YWGkqE3ND862JF8QRZERV
A fuzz campaign on real windows found that, before 0f6d155, models whose first gap extension exceeds the match score (346 or 400 against 210, trained-like matrices) gave a different MSA from the 32-bit DP with no check firing: row 0's dearer gap piece wrapped in a 16-bit run counted as proven. Divergent generated sequences reproduce it (16 of 60 inputs with -M 210 -X 1020 -O 871,2479 -E 346,2); the one added to tests/test_score_range.sh must now give the 32-bit result. f46f6c6, 7312421 and 8eeb089 fail it, at both SIMD widths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013YWGkqE3ND862JF8QRZERV
With the score-range changes the DP executes the same instructions in its loops as before (about 2% more in total), yet on this Skylake its cycle count moved with unrelated edits: 36110ac and 8eeb089..8d41b30 were level, while 1a682ec, whose only effect on the loops is to shift them by a few bytes, took 14.5% more cycles in the AVX2 build (the one cactus uses) and 5.5% more in the runtime-dispatch build. The cause is Intel's JCC erratum: a conditional jump of the scalar max_in_row loop now crossed a 32-byte boundary, which keeps the loop out of the decoded-uop cache. Built with -Wa,-mbranches-within-32B-boundaries, all three are level. Aligning loops to 32 bytes fixes where each loop sits relative to those boundaries, so its speed no longer depends on the code before it. On pinned, simultaneous pairs on a pure 32-bit window, the DP is now level with 36110ac: cycles 0.998 of it (AVX2 and dispatch, cactus's default and a trained model). The flag is added to the DP object only, on x86, and only if the compiler takes it (it is probed with -Werror). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013YWGkqE3ND862JF8QRZERV
When the bounds prove a 32-bit alignment's scores fit, a failed check cannot be an overflow, yet a failed best-score check (with or without a failed backtrack) still printed "Scores this large cannot be aligned exactly: scale them down", even for small scores (-M 89 -X 2279 -O 7100,2696 -E 110,52 -b 0 -f 0 -S on a 51 bp query, which also kills 36110ac with "Error in cg_backtrack"). Any failure at a proven 32-bit width now says the scores are in range, as a backtrack-only failure already did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013YWGkqE3ND862JF8QRZERV
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
With large scores, abPOA can stop with
Error in cg_backtrackor, worse, return a wrong MSA without any error. Cactus hit this on a CHM13 HPRC build: chr2's trained scoring model reached abPOA as-O 1290,3870 -E 60,2(match up to 210, mismatch down to −1020), andcactus_consolidated(chr2)died 9 times out of 9. This PR replaces the 16/32-bit choice with one worked out from bounds on the scores. It checks any alignment whose width it cannot prove safe, and redoes a failed 16-bit alignment at 32 bits. Output is unchanged wherever the old code was right.The bug:
max_score <= INT16_MAX - min_mis - gap_oe1 - gap_oe2) budgets the range as if the "minus infinity" floor sat atINT16_MIN + max(min_mis, oe1, oe2). Upstream Backtrack errors when trying different scoring matrices yangao07/abPOA#79 (add820b, Oct 2024) raised the floor by512*max(e1, e2)but did not take that out of the gate's budget.Error in cg_backtrack) or retraces a wrong path.512*ewastes much of the 16-bit range and can be too small at 32 bits (AVX-512 with trained scores).What changes (
src/abpoa_align_simd.c,Makefile):Width rule:
simd_abpoa_score_range()works out, per width and in 64-bit arithmetic, how far floor-derived values can move from the floor:SIMD_SET_Fstep;It puts the floor just high enough that nothing wraps. It uses 16 bits only if every real score stays above the zone that floor-derived values can reach.
abpt.Proven or checked: when every row covers the whole query, the lower bound on real scores is rigorous, and the width is proven safe. With a narrower band the bound is only an estimate, so the alignment is checked.
Checks:
err_fatal;If none of these fires, the result is the exact DP.
Retry: a 16-bit alignment that fails a check is redone at 32 bits. Before the retry,
resand the per-node band positions (node_id_to_max_pos_left/right) are restored.32 bits:
3X + M + 3OE + (pn/2)*EM + 5PS >= 2^31.Row 0: each gap piece's
-(o + e*i)is computed in 64 bits and clamped at the floor. Before this, a first gap extension larger than the match score (346 against 210, say) could wrap the dearer piece in a 16-bit run counted as proven, and give a wrong MSA with no check firing. At 32 bits the same products could overflowint.Loop alignment (
e2aa0f1, a separate commit that can be dropped): on x86 the DP object is built with-falign-loops=32if the compiler accepts it. On Skylake-class CPUs (Intel's JCC erratum), the DP's speed otherwise depends on where unrelated edits happen to place its loops. Without it, this code took 15% more cycles than 36110ac on a Skylake i7-6700, although it executes only 2% more instructions.Other: the public API is unchanged. Error messages give the M and X the rule used, and a failure at a 32-bit width the bounds prove says the scores are in range, since it cannot be an overflow. At debug verbosity a
score width: N bitsline is printed. A test-only build (-DABPOA_TEST_SCORE_RANGE) reads environment variables that force 32 bits or a 16-bit floor that loses.Results (MSA and consensus vs 36110ac; cactus's settings: convex gaps, global mode,
-b 1000 -f 0.1, progressive; AVX2 build, as cactus builds abPOA):D is cactus's default (
-O 400,1200 -E 30,1, match ≤ 100). T1 is a trained model with gap extension 30 (-O 1410,4230 -E 30,1, match ≤ 210). T2h is the chr2 model with every score halved (-O 645,1935 -E 30,1).-E 60,2) on the 2,327 yeast, chr10 and short windows: this PR gives the T2h result on all 2,327. 36110ac aborted on 1,030 and gave a wrong MSA on 860.Tests:
tests/run_all.shhas a new regression block:test_data/seq.fawith the chr2 scores must equal the same run with every score halved, under three band settings. 36110ac aborts on it.tests/test_score_range.shis a new file, whichrun_all.shpicks up with the othertest_*.shscripts; it takes about 2 minutes. It builds abPOA with-DABPOA_TEST_SCORE_RANGEand checks:tests/run_all.shpasses 12/12, in about 2 minutes. The new checks fail on 36110ac.Merging: this targets
main, whose tip is 36110ac, the commit cactus pins today. The fork's default branch,master, is the July 2021 v1.2.5 merge and is not what cactus uses. Please merge with a merge commit, not a squash or rebase: cactus will pin this branch's tip, 12f99c4, which must stay reachable.🤖 Generated with Claude Code
https://claude.ai/code/session_013YWGkqE3ND862JF8QRZERV