Repository navigation
fix(householder): split multiple-block factor upgrades at a block boundary (#265) - #343
Conversation
…ndary upgrade_householder_factor's multiple-block branch split the factor and the basis at `block_count / 2`, a block count passed to row/column splits, so a half could start inside a target block. At n = 9, block_size = 2, prev_block_size = 1 it left T[4, 5] and T[6, 7] unwritten and wrote T[7, 8] between blocks. Split at `(block_count / 2) * block_size`, the rule the previous-block branch already uses; the join_raw team split is unchanged. Adds faer/tests/public_householder_upgrade.rs (f32/f64/c32/c64, Seq and 2/4-thread teams, odd block counts, short last blocks, previous widths 1/2/4/8): each within-block coefficient, the denominators and every entry outside the blocks are checked separately. Registers it in the std,linalg CI job. Records the fix in PATCHSET.md and a worklog. Closes #265 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GPT-6.1-sol adversarial reviewCRITICAL: none Critical (must not merge)none Needs changes
Unverified claims
Checked and fine
Reviewed head: |
…dger's work claim The sequential and team tests now collect the failures of all shapes before asserting, so one run shows each shape fails or passes on its own instead of stopping at the first. PATCHSET.md no longer says the fix keeps the same products: before it the n = 9 leaves formed (0,1), (2,3), (7,8), and now (0,1), (2,3), (4,5), (6,7). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GPT-6.1-sol adversarial reviewCRITICAL: none Critical (must not merge)none Needs changesnone Unverified claims
Checked and fine
Reviewed head: |
Fixes the multiple-block branch of
upgrade_householder_factor, which split the square factor at a number of blocks where it needed a column offset.Cause
faer/src/linalg/householder.rs:198(mainf3873492) computedmid = block_count / 2and passedmidtosplit_at_mut(mid, mid),split_at_col(mid)andsplit_at_row(mid)(l.199-201). Those take a row/column offset. So onceblock_count >= 2, a half could start inside a target block. Pairs that straddled the split were never written, and the second half's block grid was shifted, so it wrote coefficients between blocks. In the issue's case (n = 9,block_size = 2,prev_block_size = 1) the recursion splits at global columns 2, 4, 5, 6 and 7 (each half re-counting its own blocks from its first column). Pairs (4,5) and (6,7) straddle a split, so they are never formed, and columns {7,8} become a leaf of their own, which writes (7,8). Those are exactly the three entries the issue reports.The previous-block branch of the same function already splits on a boundary:
mid = (prev_block_count / 2) * prev_block_size(l.343). The fix uses that rule for target blocks:mid = (block_count / 2) * block_size. The two halves still go tojoin_rawwith the team split it makes; no width or floor changes.Scratch side: on main this function has no scratch query (its row-split partials are allocated internally, see #93), and no
*_scratchfunction mirrors this recursion, so there is no second partition to align. The scratch API from the closed #267 is not part of this PR.Callers: every in-tree caller (blocked QR, col-pivoting QR, Hessenberg, tridiagonal, bidiagonal) passes a factor exactly
block_sizewide. They only reach the single-block branch, so their results are not expected to change.The same
mid = block_count / 2is in upstream Codeberg0ecd846(householder.rs:158). I found this by reading the source; I did not run it there. It is recorded in the worklog under the deferred-feedback rule, and nothing was sent upstream.Changes
faer/src/linalg/householder.rs: the one-line split fix plus a comment.faer/tests/public_householder_upgrade.rs(new), a public-API test:issue_265_reproduction: the issue's MWE as filed (f64, Seq).several_blocks_{f32,f64,c32,c64}: run onPar::Seq. Cases are the issue's shape (4 blocks + 1-column tail), square and tall; two full blocks with no tail; 3 blocks (4,4,3) overprev = 2; 5 blocks (8x4+5) overprev = 4; 5 blocks (16x4+6) overprev = 8, which takes the branch that keeps the previous blocks' coefficients. Controls that pass before and after:prev == block_size(a no-op) and single blocks whereprev = 4does not divideblock_size = 6.several_blocks_on_a_team: 4096-row cases (n= 9, 11, 16, 37, 70) at all four scalars on 2- and 4-thread teams. Then = 37andn = 70cases must open team regions (checked withfaer::utils::team::stats).several_blocks_reject_a_previous_size_that_does_not_divide: with several blocks andprevnot dividingblock_size, the call still panics. The test pins that.V^H Vformed inc64, within4 m eps sum_k |V_ki||V_kj|. Diagonal (denominators), lower triangle and between-block entries must stay exactly equal to a sentinel/tau. The basis's diagonal and upper part hold garbage, so any read of them would fail the test..github/workflows/ci.yml: adds--test public_householder_upgradeto thestd,linalgjob. The team test is behindcfg(feature = "threadteam"), so that job runs the other six.PATCHSET.mdrow anddocs/worklogs/2026-11-09-householder-upgrade-block-offset.md.Verification
Host: aarch64 macOS (Apple Silicon), toolchain 1.96.0 (pinned).
Red: the new test at head
520f6c5dwithhouseholder.rsreverted to mainf3873492(fix not applied). Each test reports every failing shape, so this one run shows each case on its own. Theissue_265_reproductionoutput is the same three entries the issue reports.All 7 multiple-block shapes fail at all four scalars on
Par::Seq, and all 40 team runs fail (5 shapes x 4 scalars x widths 2 and 4). The three controls (prev == block_sizeand the two single-block shapes) are not listed because they pass with or without the fix.Green at
520f6c5d:Two failures in the same run that main has too:
lq_no_alloc::lq_allocates_nothing_60x300fails with[13, 12, 17, 17]allocations. Unmodified main gives the same counts on this host. This is the aarch64 SME pack-buffer case described infaer/tests/lq_no_alloc.rs:17-29.no_step_alloc::parallel_regions_allocate_nothingfailed once in the combined run (full pivoting LU, width 4: 2). LU does not call this function. It then passed 5/5 alone with the fix and 5/5 on main.cargo fmt --checkreports nothing for the changed lines or the new file. The repo-wide fmt and clippy jobs arecontinue-on-errorand already report many hits on main.Not measured: timing. The fix changes which coefficients are formed (at
n = 9, before:(0,1), (2,3), (7,8); after:(0,1), (2,3), (4,5), (6,7)). No dispatch or floor code was edited.The related integration-test run was at
b3957170. The only later change is in the new test file, sohouseholder.rsis identical there.Closes #265
🤖 Generated with Claude Code