Repository navigation
fix(householder): gate the block apply's row split on the top block - #13
Merged
Merged
Conversation
The row split sums `tmp = V^H C` from partials over the target's row chunks. The first `n` rows of `V` are its unit-lower triangular head and their contribution is applied through the head's own `n x n` factor, so a chunk whose rows are a strict subrange of `[0, n)` needs that factor's columns `lo..top_hi` -- a unit-upper trapezoid no `triangular::matmul` precondition can state. The predicate asked only for depth, so any shape with `ceil(M / threads) < n` fed the full `n x n` factor against a shorter row chunk and tripped `lhs_ncols == rhs_nrows` (64 against 32) in `qr_in_place`, which is #12's abort. One conjunct, `M.div_ceil(threads) >= n`, keeps the split exactly where one chunk swallows the whole top block. Every shape the row split was measured on has `chunk >= n` and is unchanged.
shinaoka
force-pushed
the
fix/issue12-triangular-assert
branch
from
October 14, 2026 03:58
d3cc762 to
3497739
Compare
shinaoka
added a commit
to tensor4all/faer-benchmark
that referenced
this pull request
Oct 14, 2026
…e LU floor The recorder materialises the pinned revision in the checkout it is handed and records it as `library.commit`, so the pin *is* what a run measures and what names the page. The first page is to record the fork's current `main` (`bfe4041`), which carries tensor4all/faer#13 (the row-split gate for the block Householder apply), #14 (the blocked LU's trailing-update floor) and the two records from #15 / #17, so the pin moves before the run rather than with it. No page exists for this campaign yet, so nothing becomes stale by this move.
shinaoka
added a commit
to tensor4all/faer-benchmark
that referenced
this pull request
Oct 14, 2026
…e LU floor The recorder materialises the pinned revision in the checkout it is handed and records it as `library.commit`, so the pin *is* what a run measures and what names the page. The first page is to record the fork's current `main` (`bfe4041`), which carries tensor4all/faer#13 (the row-split gate for the block Householder apply), #14 (the blocked LU's trailing-update floor) and the two records from #15 / #17, so the pin moves before the run rather than with it. No page exists for this campaign yet, so nothing becomes stale by this move.
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.
Fixes the assertion that blocked the campaign's
householder_applypage(tensor4all/faer#12).
Owner: the library, not the harness
The abort is reachable with no harness at all.
qr_in_placeon a 2048x2048f64at 8 threads with its ownrecommended_block_size(64) aborts, and so does4096x4096 at 4 and 8 threads, before any
householder_applycall runs:The harness's own fixture is legal: it passes
block_size = min(m, n)toqr_in_place_scratch/qr_in_place, whose only contract on that argument is> 0andQ_coeff.ncols() == min(A.nrows(), A.ncols()). The campaign's 256x256case is that shape, and the assertion fires inside
qr_in_place-- the applythe harness measures never runs.
Cause
The row split in
apply_block_householder_on_the_left_in_place_generic(fork-only:fa22a69,c061812,c8fea58) sumstmp = V^H Cfrom partials over thetarget's row chunks. The first
nrows ofVare its unit-lower triangular headand their contribution is applied through that head's own
n x nfactor, so achunk whose rows are a strict subrange of
[0, n)would need the head factor'scolumns
lo..top_hi-- a unit-upper trapezoid, a shapetriangular::matmulcannot state. The predicate asked only for depth, so any shape with
ceil(M / threads) < nhanded the fulln x nfactor to a shorter row chunk.The campaign's 256x256 QR reaches it through a panel apply with
M = 256,n = 64,K = 64and 8 threads (chunk = 32).Fix
One more conjunct,
M.div_ceil(threads) >= n, so the split is taken only whileone chunk swallows the whole top block. No product is computed differently.
Every shape the split was measured on (a 4096-row target with a 16-wide basis,
chunk = 512; the recorded tests 2048x4 and 4096x16) haschunk >= nand isunchanged. A column-sliced head factor would keep the split for narrower panels,
at the cost of two products and an explicit zero fill; rejected for now, recorded
in the work log.
Verification
threaded_panel_narrower_than_the_row_chunk_matches_the_sequential_one(256x128 basis, 64-column target, 4 threads,
chunk = 64 < n = 128): on theparent it fails with
lhs_ncols = 128, rhs_nrows = 64, with the gate it passes.cargo test -p t4a-faer --lib: 204 passed, 0 failed, 2 ignored. No pre-existingfailure. In particular
sparse::linalg::qr::tests::test_numeric_simplicial_qr_1_transpose,which the issue names as failing on 22f23cf, passes on both 22f23cf and 9de95f7 --
its calls are all
Par::Seq, so it cannot reach the split; that secondreproducer in the issue was misattributed.
verifyover the whole declaredhouseholdergrid (4, 8, 16, 32,64, 256, 1024x64, 4096x256; f64/c64; both orders; 1, 4 and 8 threads; fork and
upstream rows): "all comparisons within tolerance", exit 0. That is the check
record_run.pymakes before publishing, so the population is recordable again.fbenchbuilt withscripts/build_for_faer_rev.sh --vendor mklatthis branch's commit against
/tmp/upstream-faer(faer-v0.24.4); every build,test and measurement pinned to CPUs 4-11. No timing was claimed.
Work log:
docs/worklogs/2026-10-14-row-split-top-block-gate.md, with thePATCHSET.mdrowRow-split gate for the block Householder apply's top block.The row split is fork-only, so nothing upstream was measured or reported.