Skip to content

docs(#327): link the fixed-thread-count reproducibility contract, and stop asserting cross-count bitwise equality - #334

Merged
shinaoka merged 10 commits into
mainfrom
docs/327-thread-count-reproducibility
Nov 9, 2026
Merged

shinaoka merged 10 commits into
mainfrom
docs/327-thread-count-reproducibility

Conversation

@shinaoka

@shinaoka shinaoka commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

Summary

docs/reproducibility.md already states the contract that #327 fixed (it landed with #328): bitwise-identical
results on the same machine and build at a fixed thread count, and nothing across thread counts. This change
links it from the API docs and makes three tests assert what the project actually promises instead of exact
equality between a Par::Seq result and a team result. It closes #327.

The promise itself is the one in docs/reproducibility.md (owner decision on #327); this PR links it and does
not re-establish it. What the tests here check is narrower, and the sections below say which part each one covers.
No test in this repository compares a second process, and nothing added here changes that.

Changed (docs and tests only; no implementation or public API change)

file:line change
faer/src/lib.rs:183-190 the # parallelism crate-doc section states the promise and links docs/reproducibility.md (#327); the reason it gives is that a width change can change both the path a call takes and how the work is partitioned (the tridiagonalization splits by the participant count, faer/src/linalg/evd/tridiag.rs:386-409)
faer/tests/pfaffian.rs:397-409 the bound is proportional to the result, `eps x 200 x n x
faer/tests/generalized_schur_reorder.rs:555-561 team_case compares the four factors with the file's close tolerance instead of assert_eq!
faer/tests/common/mod.rs:22-31 close refuses a non-finite residual or bound before comparing them: with an infinity on one side both are inf, and inf <= inf accepted the pair. Shared by nine test binaries
faer/tests/generalized_schur_reorder.rs:565-573 close_rejects_an_infinite_reference: an 8x8 identity against a copy with (0, 0) = +inf, should_panic on "non-finite"
faer/src/linalg/matmul/mod.rs:2477-2510 test_matmul_with_conj_uninit_team builds its reference with the same team and compares with ==; the serial product is compared against a bound scaled by the product's largest entry, and a shrunken copy of the same product checks that the bound rejects an all-zero result. An all-zero reference is outside the test: assert!(scale > 0.0) at :2488 rejects it before any comparison, as the comment at :2479-2483 now says
faer/src/utils/team.rs:870-886 the helper max_diff accepts a non-finite difference only between two equal infinities; two NaNs of the same bits fail it (tests at :888-900)
faer/src/utils/team.rs:862-868 the new max_finite folds each difference into a running maximum and refuses a non-finite one, used by every manual comparison in the team tests
faer/src/utils/team.rs:973-977 max_finite_rejects_a_nan pins that guard directly, should_panic on "non-finite difference"
faer/src/utils/team.rs:903-913 the families test's tolerance is now the function assert_within_tolerance; its scale is the largest finite magnitude of the reference (:908-911), so an infinity sentinel no longer makes the bound infinite. max_diff still requires such an entry to be the same infinity on both sides
faer/src/utils/team.rs:918-944 the two QR fixtures that carry an infinity (small_qr, the 32x32 QR whose q holds +inf at (3, 31); col_piv_qr, the families test's 200x200 column pivoting QR) are functions, shared with small_qr_on_a_team_is_the_sequential_one and the families table (:1170)
faer/src/utils/team.rs:946-968 tolerance_rejects_a_corrupted_col_pivoting_factor and tolerance_ignores_an_infinite_reference_entry: each fixture against a copy whose (0, 0) is off by 1e6, should_panic on the tolerance message
faer/src/utils/team.rs:999-1005, :1948-1950 two exact comparisons stay, each with a comment on why they are not cross-count claims
faer/tests/lq_no_alloc.rs:18-30 records where the one failing cell of that binary comes from on this host

Two notes on wording, because an earlier version of this body overstated them:

  • assert!(result == expected) in the matmul test is representation equality at one width, not bit equality:
    == treats +0.0 and -0.0 as equal.
  • The max_diff(...) == 0.0 comparisons that stay (a small QR whose team opened no section; a team of one
    participant) do not distinguish +0.0 from -0.0 either. Both sides run the same one-participant partition
    there, which is why an exactly-zero difference is the right assertion.

Why the pfaffian bound changed

At 620b96a this test asserted assert_eq!(got, want); what it asserts now is a bound proportional to the
result. The floor of 1 is the alternative that was rejected, and its cost is measured — the four fixtures this
test runs, at n = 258, bound values printed by the same build:

| precision | |want| | bound with a floor of 1 | bound as committed | floor is looser by | relative error the floor admits |
| --- | --- | --- | --- | --- | --- |
| f64 | 3.0703979537356053e-21 | 1.1457501614131615e-11 | 3.5179089510952106e-32 | 3.2569e20 | 3.7316e9 |
| f32 | 3.0704116e-21 | 0.0061511993 | 1.8886714e-23 | 3.2569e20 | 2.0034e18 |
| c64 | 0.1610874696191822 | 1.1457501614131615e-11 | 1.8456599431781575e-12 | 6.2078 | 7.1126e-11 |
| c32 | 0.16108781 | 0.0061511993 | 0.0009908832 | 6.2078 | 0.0382 |

So for f64 the floor admits a relative error of ~3.7e9, and for f32 ~2.0e18 — at f32 it is not a loose
bound but effectively no bound at all. The complex fixtures are not indifferent to it either: their |want| is
0.1611, below one, so the floor was 6.21 times the bound there. (Two corrections from the review of
ce7ec19: the earlier version of this section said the floor "never mattered" for the complex fixtures, which is
numerically wrong, and it described that floor as the previous bound, which no commit on this branch carried.)

Measured difference per (scalar, width)

cargo test -p t4a-faer --features threadteam --release --test pfaffian team_agreement_and_actual_row_dispatch -- --nocapture
prints one line per case; the values below are copied from that output, not rounded. The test asserts the bound,
not equality with zero — the zero is recorded, so that the claim can be re-measured rather than believed:

f64 width=1 diff=0.0 want=3.0703979537356053e-21
f64 width=3 diff=0.0 want=3.0703979537356053e-21
f64 width=8 diff=0.0 want=3.0703979537356053e-21
f64 width=32 diff=0.0 want=3.0703979537356053e-21
f32 width=1 diff=0.0 want=3.0704116e-21
f32 width=3 diff=0.0 want=3.0704116e-21
f32 width=8 diff=0.0 want=3.0704116e-21
f32 width=32 diff=0.0 want=3.0704116e-21
num_complex::Complex<f64> width=1 diff=0.0 want=Complex { re: 0.120180331711807, im: -0.10726537529860687 }
num_complex::Complex<f64> width=3 diff=0.0 want=Complex { re: 0.120180331711807, im: -0.10726537529860687 }
num_complex::Complex<f64> width=8 diff=0.0 want=Complex { re: 0.120180331711807, im: -0.10726537529860687 }
num_complex::Complex<f64> width=32 diff=0.0 want=Complex { re: 0.120180331711807, im: -0.10726537529860687 }
num_complex::Complex<f32> width=1 diff=0.0 want=Complex { re: 0.12018082, im: -0.107265346 }
num_complex::Complex<f32> width=3 diff=0.0 want=Complex { re: 0.12018082, im: -0.107265346 }
num_complex::Complex<f32> width=8 diff=0.0 want=Complex { re: 0.12018082, im: -0.107265346 }
num_complex::Complex<f32> width=32 diff=0.0 want=Complex { re: 0.12018082, im: -0.107265346 }

Mutation checks

Each fix below was measured by breaking the line it fixes, at 0136bca or at the head it was written on;
those lines have not changed since.

1. A NaN through max_diff (the exception put back to to_bits() equality):

$ cargo test -p t4a-faer --release --features threadteam --lib utils::team::tests::max_diff
test utils::team::tests::max_diff_accepts_two_equal_infinities ... ok
test utils::team::tests::max_diff_rejects_a_nan_on_both_sides - should panic ... FAILED
note: test did not panic as expected at faer/src/utils/team.rs:892:5

With the to_bits() exception a 1 x 1 NaN against itself reads as max_diff = 0.0; with the
equal-infinities exception it panics with 0,0: NaN and NaN differ by NaN, and two +inf still read as 0.0,
which is what the 32x32 QR fixture needs at q[(3, 31)].

2. An all-NaN tiled product (got filled with NaN just before the comparison):

$ cargo test -p t4a-faer --features threadteam --release --lib utils::team::tests::gemm_tiled
thread 'utils::team::tests::gemm_tiled_matches_the_gemm_crate_for_every_dtype' panicked at faer/src/utils/team.rs:866:9:
non-finite difference NaN
test result: FAILED

The same injection against the previous fold(0.0, f64::max) passes, which is the hole this closes: f64::max
returns its other operand when one side is NaN, so an all-NaN output satisfied the 1e-3 tolerance. The same two
directions measured on the triangular comparison (got_l/got_u filled with NaN in
triangular_products_on_the_team_match_sequential_and_stay_on_it): max_finite → FAILED, the previous
d.max(...) → ok. The complex triangular product (:1990-1996) and the spicy_matmul comparisons
(:2019-2025, :2234, :2296) sit in x86_64-only tests, so their call sites are converted but could not be
mutated on this aarch64 host.

3. A relative error the floor would have admitted (the complex fixtures given a deliberate 3e-11 relative
error, got + want * 3e-11):

with the floor restored (bound 1.1457501614131615e-11):     test result: ok
as committed (bound 1.8456599431781575e-12):                test result: FAILED
width=1: Complex { re: 0.12018033171541241, ... } against Complex { re: 0.120180331711807, ... }, bound 1.8456599431781575e-12

So the floor was not inert on the complex fixtures: it admitted an error 6.21 times larger than the bound that
ships.

4. The matmul bound floor (from the earlier round, still valid at this head, unchanged since ce7ec19): with
fold(0.0f64, ...) restored to fold(1.0f64, ...), test_matmul_with_conj_uninit_team fails with
0.00000000000000000101 against 0.000000000001: the bound admits an all-zero product.

5. The max_finite guard (the assert! deleted from max_finite):

$ cargo test -p t4a-faer --release --features threadteam --lib utils::team::tests::max_finite
test utils::team::tests::max_finite_rejects_a_nan - should panic ... FAILED
note: test did not panic as expected at faer/src/utils/team.rs:906:5
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 849 filtered out

Without the guard max_finite(0.0, f64::NAN) returns 0.0, which is the false pass the guard prevents;
with it the test passes (3 passed; 0 failed for utils::team::tests::max_).

All five mutations were reverted; git status is clean at 0136bca.

What the tests do and do not establish

  • Established: two routes computing the same result at one build and one thread count agree within eps x 200 x n
    scaled by the result; where an exact comparison is kept, both sides run the same partition, so the difference is
    zero by construction.
  • Not established: bitwise reproducibility across processes, or after a restart, or across thread counts.
    docs/reproducibility.md:15-19 says no test in this tree compares a second process; this PR does not add one and
    does not claim the property on the strength of these tests.

Verification

Head 6ae77d2: the needs-change of the review of 7f6afed

aarch64 macOS host, rust-toolchain.toml = 1.96.0. Output copied from the runs.

faer/tests/common/mod.rs close accepted error = inf against bound = inf. The new test fails without the
fix (git stash push -- faer/tests/common/mod.rs, test file kept):

$ cargo test -p t4a-faer --release --features threadteam --test generalized_schur_reorder close_rejects
running 1 test
test close_rejects_an_infinite_reference - should panic ... FAILED
failures:
---- close_rejects_an_infinite_reference stdout ----
note: test did not panic as expected at faer/tests/generalized_schur_reorder.rs:569:4
failures:
    close_rejects_an_infinite_reference
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 31 filtered out; finished in 0.00s

With the fix (git stash pop):

$ cargo test -p t4a-faer --release --features threadteam --test generalized_schur_reorder
running 32 tests
test close_rejects_an_infinite_reference - should panic ... ok
test sequential_and_team_agree ... ok
test result: ok. 32 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

close is shared, so every binary that declares mod common was run with the fix, to check that none of them
relied on comparing infinities:

$ cargo test -p t4a-faer --release --features threadteam --test generalized_schur_reorder_no_alloc \
    --test generalized_schur_reorder --test lq --test public_balance --test public_bidiagonal_svd \
    --test public_hessenberg_schur --test public_schur_eigenvectors --test public_schur_reorder \
    --test self_adjoint_generalized_eigen
tests/generalized_schur_reorder.rs          32 passed; 0 failed
tests/generalized_schur_reorder_no_alloc.rs  1 passed; 0 failed
tests/lq.rs                                  3 passed; 0 failed
tests/public_balance.rs                      5 passed; 0 failed
tests/public_bidiagonal_svd.rs               6 passed; 0 failed
tests/public_hessenberg_schur.rs             2 passed; 0 failed
tests/public_schur_eigenvectors.rs           4 passed; 0 failed
tests/public_schur_reorder.rs                9 passed; 0 failed
tests/self_adjoint_generalized_eigen.rs      9 passed; 0 failed

Hosted checks on 6ae77d2: pending; this section is updated with the run ids once Run Tests and ci complete.

Head 7f6afed: the needs-change of the review of 403f180

A comment-only change in faer/src/linalg/matmul/mod.rs:2482-2483: the comment said an all-zero reference leaves
a zero bound that demands exact equality, while assert!(scale > 0.0) (:2488) rejects that reference first. It
now says the reference is outside the test and rejected by that assert. No assertion changed, so there is no
red run to show. aarch64 macOS host, rust-toolchain.toml = 1.96.0:

$ cargo test -p t4a-faer --release --features threadteam --lib test_matmul_with_conj_uninit_team
running 1 test
test linalg::matmul::tests::test_matmul_with_conj_uninit_team ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 851 filtered out; finished in 0.00s

Superseded by 6ae77d2 before its hosted runs finished; the hosted checks are recorded for 6ae77d2 above.

Head 403f180: the two needs-changes of the review of 0136bca

aarch64 macOS host, rust-toolchain.toml = 1.96.0. Output copied from the runs.

The new tests fail without the fix. With the scale line put back to its 0136bca form
(.fold(1.0f64, |m, (i, j)| m.max(a[(i, j)].abs()))) and everything else at 403f180:

$ cargo test -p t4a-faer --release --lib utils::team::tests::tolerance
running 2 tests
test utils::team::tests::tolerance_ignores_an_infinite_reference_entry - should panic ... FAILED
test utils::team::tests::tolerance_rejects_a_corrupted_col_pivoting_factor - should panic ... FAILED
failures:
---- utils::team::tests::tolerance_ignores_an_infinite_reference_entry stdout ----
note: test did not panic as expected at faer/src/utils/team.rs:960:5
---- utils::team::tests::tolerance_rejects_a_corrupted_col_pivoting_factor stdout ----
note: test did not panic as expected at faer/src/utils/team.rs:946:5
failures:
    utils::team::tests::tolerance_ignores_an_infinite_reference_entry
    utils::team::tests::tolerance_rejects_a_corrupted_col_pivoting_factor
test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 850 filtered out; finished in 0.00s

Each test asserts its sentinel is present before the comparison, so "did not panic" means the 1e6 error
was accepted, not that the fixture changed. With the fix:

$ cargo test -p t4a-faer --release --lib utils::team
running 34 tests
test utils::team::tests::max_diff_accepts_two_equal_infinities ... ok
test utils::team::tests::max_finite_rejects_a_nan - should panic ... ok
test utils::team::tests::max_diff_rejects_a_nan_on_both_sides - should panic ... ok
test utils::team::tests::tolerance_ignores_an_infinite_reference_entry - should panic ... ok
test utils::team::tests::small_qr_on_a_team_is_the_sequential_one ... ok
test utils::team::tests::tolerance_rejects_a_corrupted_col_pivoting_factor - should panic ... ok
test utils::team::tests::the_families_narrow_their_regions_and_match_sequential ... ok
test result: ok. 34 passed; 0 failed; 0 ignored; 0 measured; 818 filtered out; finished in 0.25s

(The seven lines are the comparison helpers and the tests that use them, picked from the 34.)

cargo clippy -p t4a-faer --all-targets exits 101 with the crate's pre-existing errors
(could not compile t4a-faer (lib) due to 40 previous errors, (lib test) due to 41), none in faer/src/lib.rs:184-188
or the changed lines of faer/src/utils/team.rs; the blocking clippy job lints t4a-faer-traits only
(code-quality.yml:34-35). cargo fmt --check reports 702 hunks at 0136bca and 703 here: the moved QR
call lines keep the file's long-line style, and CI's formatting step is advisory (code-quality.yml:26-32).
The full suite was not rerun for this change, which touches tests and a doc comment only.

Head 0136bca

Head 0136bca, on an aarch64 macOS host, rust-toolchain.toml = 1.96.0. Output copied from the runs.

$ cargo test -p t4a-faer --release --features threadteam --no-fail-fast
32 test binaries
total: 953 passed; 1 failed; 163 ignored
the one failure: test lq_allocates_nothing_60x300 ... FAILED

Per-target counts from the same run:

unittests src/lib.rs          688 passed; 0 failed; 162 ignored
tests/pfaffian.rs              12 passed; 0 failed; 0 ignored
tests/generalized_schur_reorder.rs  31 passed; 0 failed; 0 ignored
--lib utils::team::tests::     32 passed; 0 failed; 0 ignored
--lib linalg::matmul::tests::test_matmul_with_conj_uninit_team   1 passed

The one failing test

$ cargo test -p t4a-faer --release --test lq_no_alloc
thread 'lq_allocates_nothing_60x300' panicked at faer/tests/lq_no_alloc.rs:218:5:
60 x 300: allocations (f64, f32, c64, c32)
  left: [13, 12, 17, 17]
 right: [0, 0, 0, 0]

It is pre-existing and not from this change, and where it comes from is measured rather than assumed:

  • It reproduces identically at 620b96a, the base of this PR, with the same command and build settings:
    left: [13, 12, 17, 17], 7 passed and this one failed.
  • The caller, from a backtrace at the first counted allocation (probe below): dyn_stack::mem::MemBuffer::new
    inside gemm_common::gemm::PackBuffer::take, reached from gemm_f64::gemm::f64::sme::gemm_basic ←
    faer::linalg::matmul::matmul_with_conj ←
    householder::apply_block_householder_on_the_left_in_place_generic ← lq::no_pivoting::unitary::unitary.
    That is the gemm crate's aarch64 SME kernel. On x86_64 this dispatch does not exist, which is why the same
    test passes on the ubuntu-latest job of run 42550520977 for 9967d30.
  • Why those calls allocate, from the sizes of the counted allocations rather than from the kernel's name: the
    pack buffer is per thread but is only reused when the buffer in the slot can hold the request
    (t4a-gemm-common 0.20.4, Cargo.lock:2709-2712, src/gemm.rs:84-97), and Drop keeps a buffer only up to
    CACHE_INFO[1].cache_bytes (src/gemm.rs:114-129). A two-line program over the pinned version prints that cap
    on this host as L2 bytes=2796202. The 60 x 300 window allocates 3993600 and 4390912 B for f64 (and 7895040,
    8781824 B for f32), both above the cap, so each pass ends with the slot empty, and each smaller request that
    follows (57344, 114688, 65536, 131072, 147456 B) is larger than the buffer then in the slot. That is the 13
    allocations of f64, in the order measured.
  • faer/tests/lq_no_alloc.rs:18-30 records this third inherited path next to the two it already documented. An
    earlier version of this section said "the allocation counter on this machine's allocator", and the one after it
    said the kernel "takes its pack buffer per call" without saying which of the two conditions above fires; both
    are withdrawn.

The two probes, both uncommitted and reverted after the run:

// faer/tests/lq_no_alloc.rs, in `unsafe fn alloc`: the caller of the first counted allocation
if counting() {
    let n = ALLOCATIONS.fetch_add(1, Ordering::Relaxed);
    if n == 0 { eprintln!("PROBE alloc\n{}", std::backtrace::Backtrace::force_capture()); }
}

// the sizes; `IN_PRINT` is a thread-local guard, because `eprintln!` allocates inside the allocator
if counting() {
    let n = ALLOCATIONS.fetch_add(1, Ordering::Relaxed);
    if !IN_PRINT.swap(true, Ordering::Relaxed) {
        eprintln!("PROBE {n} size {} align {}", layout.size(), layout.align());
        IN_PRINT.store(false, Ordering::Relaxed);
    }
}
$ cargo test -p t4a-faer --release --test lq_no_alloc lq_allocates_nothing_60x300   # with the size probe
PROBE 0 size 3993600 align 128
PROBE 1 size 57344 align 128
PROBE 2 size 114688 align 128
PROBE 3 size 4390912 align 128
PROBE 4 size 65536 align 128
PROBE 5 size 131072 align 128
...  (the same six again for the second half of the pipeline, then one 147456)
$ cargo run --release   # the cap, in a scratch crate over t4a-gemm-common 0.20.4
L1 bytes=131072 line=64 assoc=8
L2 bytes=2796202 line=64 assoc=8

Reported rather than hidden; no --skip was added to make the run look green.

Hosted checks on the reviewed head 0136bca, all completed successfully (gh pr checks 334, each run's
headSha checked as 0136bca):

workflow run result
ci 42570326469 success (build job, 15m58s)
Run Tests 42570326466 success — testing-stable-ubuntu-latest 28m04s, which runs the numerical tests with nextest on x86_64, so it executes the x86-only team comparisons this host skips; msrv-1.89.0 and nostd-nightly also green
Assess Code Quality 42570326461 success
PR verification section 42570326763 success

Not measured: no performance claim is made, and the measured table in docs/reproducibility.md is quoted from
#327 rather than re-run here. cargo fmt is not run: the tree is formatted with nightly rustfmt options, CI's
formatting step is advisory (code-quality.yml:28-32), and a stable cargo fmt rewrites unrelated files.
cargo clippy -p t4a-faer --all-targets --features threadteam reports no diagnostic on any line this PR adds
(the crate's pre-existing diagnostics, code-quality.yml:38-40, are untouched).

Every path and line pin that docs/reproducibility.md cites (faer/src/utils/cost.rs:613-619, :711-713,
faer/src/linalg/matmul/internal/mod.rs:252-278, :289-293, faer/src/utils/team.rs:515,
faer/src/linalg/evd/tridiag.rs:386-409, :457-459, faer/tests/lu_rank_truncation.rs:183-218, PATCHSET.md:135)
resolves at this revision, and docs/new-routine-checklist.md exists and is linked.

Review

review: gpt-6.1-sol, 2026-11-09T04:08Z on ce7ec195: 0 critical, 3 needs-changes, 1 unverified — all four
addressed by the mutation checks and the base-vs-head measurement above.

review: gpt-6.1-sol, 2026-11-09T06:07Z on 0136bca7: 0 critical, 2 needs-changes, 2 unverified. Both
needs-changes are addressed at 403f180: the infinite tolerance scale (faer/src/utils/team.rs:908-911, regressions
at :946-968, red and green runs above) and the "by the problem rather than by the count" sentence
(faer/src/lib.rs:184-188). The two unverified items (hosted run of the reviewed head; the allocation trace) are
unchanged by this push.

review: gpt-6.1-sol, 2026-11-09T07:25Z on 403f180: 0 critical, 1 needs-changes, 1 unverified. The needs-change
(the all-zero-reference comment against assert!(scale > 0.0) in faer/src/linalg/matmul/mod.rs) is addressed
at 7f6afed. The unverified item (no completed numerical run on the reviewed head) is recorded under
Verification once the hosted runs finish.

review: gpt-6.1-sol, 2026-11-09T07:40Z on 7f6afed: 0 critical, 1 needs-changes, 2 unverified. The needs-change
(close accepting inf against inf, reached by the Schur team comparisons) is addressed at 6ae77d2
(faer/tests/common/mod.rs:22-31, regression at faer/tests/generalized_schur_reorder.rs:565-573). Unverified:
the hosted run of the head is recorded under Verification once it finishes; the allocation trace is unchanged by
this push.

@shinaoka

shinaoka commented Nov 9, 2026

Copy link
Copy Markdown
Member Author

GPT-6.1-sol adversarial review

CRITICAL: none
NEEDS_CHANGES: 1
UNVERIFIED: 3

Critical (must not merge)

none

Needs changes

  • faer/tests/pfaffian.rs:398-403 — The new tolerance accepts a wrong-sign Pfaffian for the real fixtures. The max(1, |want|) floor makes the bound approximately 1.146e-11 for f64 and 0.0061512 for f32, while the real 258×258, seed-37 fixture has a Pfaffian of approximately 3.0704e-21. I independently reconstructed the fixture and evaluated its Pfaffian using pivoted skew elimination; the new comparison accepts the negated reference. Reproduce in the Rust test by keeping the computation and dispatch counters, then inserting let got = if T::IS_REAL { -want.clone() } else { got }; before the comparison and running cargo test -p t4a-faer --test pfaffian team_agreement_and_actual_row_dispatch. This mutation should survive the rewritten assertion despite a 200% relative error; the Rust mutation itself was not run here. Remove the unit-scale floor for these nonzero scalar outputs, or rescale the fixtures so the tolerance actually constrains their relative error.

Unverified claims

  • faer/src/linalg/matmul/mod.rs:2459-2462,2475,2495 — “Bit for bit” is not established by matrix ==. These assertions check numerical equality, not floating-point representations. Reproduce the distinction with assert_eq!(0.0_f64, -0.0_f64) and assert_ne!(0.0_f64.to_bits(), (-0.0_f64).to_bits()). The PR does not add representation-level comparisons to establish its bitwise-testing claim.

  • faer/src/lib.rs:183-187 — The fixed-width reproducibility promise is documented, not demonstrated across fresh processes. docs/reproducibility.md:15-19 explicitly says that no repository test compares separate processes or repeats after restart. To verify that part, run the same non-integer fixture in separate processes with an explicitly fixed width and compare serialized component bits; the changed tests do not perform that experiment.

  • faer/src/linalg/matmul/mod.rs:2465, faer/tests/generalized_schur_reorder.rs:567, faer/tests/pfaffian.rs:385 — The PR’s exact local pass counts and “warnings already on main” claim remain unverified. I cannot read the original local command transcripts or a matching baseline warning comparison. Hosted run 42546068836 used the reviewed head and ran these three tests, but its --features=unstable coverage invocation does not establish the separately reported local counts or warning provenance.

Checked and fine

  • Run 42546068836 targets head 06c73c1b9f6e41cb54401a9dc150a1967779bc40; all three rewritten tests appear as executed PASS entries, not skips.
  • No repository files changed; Cargo tests and the Rust mutation were not run locally, so the mutation result remains unconfirmed.
  • Cache repair/verification completed: 64,725 valid entries, zero corrupted; unrelated stale compiler shims remain.

Automated (gpt_review.sh). This is an enumeration of counterexamples, not a verdict; the merge decision stays with a human.

… link in the crate docs

#327 withdrew the cross-thread-count bitwise requirement: the promise is
bitwise-identical results on the same machine and build at a fixed thread count, and a
comparison across counts uses the family's tolerance. `docs/reproducibility.md` (landed with
#328) states that; this change makes the tree follow it and links it from the API docs.

Three tests asserted exact equality between a `Par::Seq` result and a team result:

- `tests/pfaffian.rs::team_agreement_and_actual_row_dispatch` compares the Pfaffian with a
  bound proportional to the result, `eps x 200 x n x |want|`. The fixtures return ~3e-21
  (f64, f32) and ~0.12 (c32, c64); the previous absolute floor made the bound ~1e-11 there,
  a 1e9 relative error that accepted a negated reference. A negated reference now fails
  (`width=1: -3.070397953735666e-21 against 3.070397953735666e-21, bound 3.51790895109528e-32`).
- `tests/generalized_schur_reorder.rs::team_case` (run by `sequential_and_team_agree`)
  compares the four factors with the file's `close` tolerance.
- `linalg::matmul::tests::test_matmul_with_conj_uninit_team` builds its reference with the
  same team and compares with `==` (which treats `+0.0` and `-0.0` as equal, so it is
  representation equality at one width, not a bitwise claim), and compares the serial
  product against a bound scaled by the product's largest entry.

Two exact comparisons stay, each with a comment saying why they are not cross-count claims:
a small QR on a team that provably opened no section, and a team of one participant.

The `# parallelism` crate docs now state the promise and link `docs/reproducibility.md`.

No implementation or public API change; every changed test verifies what it did before.
@shinaoka
shinaoka force-pushed the docs/327-thread-count-reproducibility branch from 06c73c1 to 9967d30 Compare November 9, 2026 02:21
@shinaoka

shinaoka commented Nov 9, 2026

Copy link
Copy Markdown
Member Author

GPT-6.1-sol adversarial review

CRITICAL: none
NEEDS_CHANGES: 3
UNVERIFIED: 3

Critical (must not merge)

none

Needs changes

  • NaNs pass the retained “exactly-zero difference” checks — inherited defect. faer/src/utils/team.rs:862-869, used at :907-908: f64::max ignores a NaN operand, so a corrupted entry contributes no error; reproduce by making a_team mutable and inserting a_team[(0, 0)] = f64::NAN immediately before line 907—the comparison still computes zero when the remaining entries agree. The new justification at :902-906 overlooks this failure mode; reject non-finite differences in the shared helper. This mutation outcome follows from source inspection, not an executed Rust test.

  • The new matmul bound retains the floor its comment says it avoids. faer/src/linalg/matmul/mod.rs:2479-2488: .fold(1.0f64, ...) computes max(1, largest_entry), not just the product’s scale; reproduce analytically by multiplying the existing lhs fixture by 1e-20, which makes the reference’s largest entry 1.01e-18, while the implemented bound remains 1e-12 and therefore accepts an entirely zero result. The current unscaled fixture has maximum magnitude 101, so it hides this discrepancy; initialize the fold with zero to implement the stated bound.

  • The PR’s f32 relative-error calculation is wrong by roughly nine orders of magnitude. faer/tests/pfaffian.rs:401-403, PR body “Why the pfaffian bound changed”: using the body’s stated f32 reference, evaluate (2**-23 * 200 * 258) / 3.0704421e-21; the old floored bound permits approximately 2.003e18 relative error, not approximately 1e9. The f64 calculation gives approximately 3.732e9; distinguish the two precisions in the rationale.

Unverified claims

  • Fixed-width bitwise reproducibility across processes: faer/src/lib.rs:183-187 links the promise in docs/reproducibility.md:7-19, but the changed tests do not establish it; verification requires running the same fixed-width computation in separate processes and comparing output bits. The documentation explicitly acknowledges this gap.

  • Exactly zero Pfaffian differences for every stated width and scalar: faer/tests/pfaffian.rs:388-421 neither asserts zero difference nor prints successful residuals, so the quoted passing test output cannot establish the body’s “exactly zero” measurement; reproduce the measurement by recording diff for all sixteen scalar/width combinations.

  • Completed CI validation: .github/workflows/run-tests.yml:123-124 contains the numerical test execution, but when inspected, run 42550520977 for head 9967d30c5dbf71ec144cbaf11e2ead79421f6fd1 still had its coverage/test step in progress, and build run 42550520973 was also in progress; the completed MSRV checks at :39-44 only run cargo check, not these tests.

Checked and fine

none

Not checked: Rust tests and mutation commands were not executed; numerical calculations above were checked separately.


Automated (gpt_review.sh). This is an enumeration of counterexamples, not a verdict; the merge decision stays with a human.

@shinaoka shinaoka self-assigned this Nov 9, 2026
…record the pfaffian diffs

Three findings from the review of #334, all in test code.

`test_matmul_with_conj_uninit_team` folded the scale from 1.0, so the bound was
`1e-12 * max(1, largest_entry)` while the comment next to it claimed a relative
bound. It now folds from 0.0, asserts the reference is not all zero (a zero
reference leaves a zero bound, which demands exact equality), and the same test
shrinks the product by 1e-20 where an all-zero result must exceed the bound,
which is exactly what the floor made impossible.

`max_diff` used `f64::max`, which returns its other operand when one is NaN, so
a NaN entry read as agreement. A non-finite difference now passes only where
both entries are the same bits, which is what the `+inf` at `q[(3, 31)]` of the
32x32 fixture is on both routes.

`team_agreement_and_actual_row_dispatch` prints the difference and the reference
per (scalar, width), so "the difference is exactly zero at every width" is a
recorded measurement rather than a claim in the description.
@shinaoka
shinaoka marked this pull request as ready for review November 9, 2026 03:54
@shinaoka

shinaoka commented Nov 9, 2026

Copy link
Copy Markdown
Member Author

GPT-6.1-sol adversarial review

CRITICAL: none
NEEDS_CHANGES: 3
UNVERIFIED: 1

Critical (must not merge)

none

Needs changes

  • Matching NaNs still count as zero error — faer/src/utils/team.rs:871-877: the bit-equality exception accepts two identical NaNs, after which d.max(NaN) preserves d, so two corrupted results can pass every caller’s numerical comparison. Reproducer: call max_diff on two 1 × 1 matrices containing f64::NAN; an executed probe using the extracted helper returned 0.0. Alternatively, inject a[(0, 0)] = f64::NAN after qr_in_place inside the small_qr_on_a_team_is_the_sequential_one test’s shared run closure, corrupting both routes. Restrict the exception to matching infinities; matching NaNs must fail.

  • The same NaN-swallowing defect remains in the tiled-GEMM checks — faer/src/utils/team.rs:2059-2060, also :2001-2006: these reductions still use f64::max without checking whether each error is finite, allowing an entirely NaN result to satisfy the tolerance. Reproducer: insert got.fill(from(f64::NAN)); immediately before line 2059 and run cargo test -p t4a-faer --features threadteam --release --lib utils::team::tests::gemm_tiled_matches_the_gemm_crate_for_every_dtype; the reduction discards every NaN error. An executed standalone probe of that reduction returned 0.0 for finite references against all-NaN output and satisfied d < 1e-3. Check each error before reducing, including the triangular comparison.

  • The PR’s complex-fixture explanation is numerically false — faer/tests/pfaffian.rs:402-404, PR body’s “Why the pfaffian bound changed”: the statement that the complex fixtures are approximately 0.12, “where the floor never mattered,” contradicts the stated formula. Reproducer: calculate hypot(0.120180331711807, -0.10726537529860687) = 0.16108746961918222; consequently, max(1, |want|) is 1, and removing that floor tightens the bound by approximately 6.2078×. Correct the explanation; the floor affected these fixtures too.

Unverified claims

  • The allocator-specific diagnosis is not established — faer/tests/lq_no_alloc.rs:204, PR body’s Verification section: reproducing the allocation failure on macOS while Ubuntu passes does not identify “this machine’s allocator” as its cause. The reproduction transcript and an allocator trace are not supplied as readable artifacts; cannot read them. Reproduce the test on base 620b96a and head with the same build settings, then trace the counted allocations before assigning that cause.

Checked and fine

  • All four cited hosted runs report success for head ce7ec1955c65b1fadfdea6444e7760e5c0ca5356; the numerical-job log contains PASS entries for the four directly affected tests and reports 870 passed, 162 skipped.
  • Recalculation matches the PR’s f32/f64 old bounds, relative-error ratios, and new bounds.
  • Not rerun: full Cargo suites or repository-level mutations; executed probes cover the comparison defects, not a rebuilt numerical implementation.

Reviewed head: ce7ec1955c65b1fadfdea6444e7760e5c0ca5356 at 2026-11-09T04:03:12Z. This review is valid only for that commit: if the head has moved, it is a review of a tree that is not being merged.
Automated (gpt_review.sh). This is an enumeration of counterexamples, not a verdict; the merge decision stays with a human.

…ds cost

Three findings from the review of #334.

`max_finite` folds one difference into a running maximum and refuses a
non-finite one. `f64::max` returns its other operand when one side is NaN, so an
all-NaN output satisfied every tolerance these tests compare against: with `got`
filled with NaN, `gemm_tiled_matches_the_gemm_crate_for_every_dtype` passed at
the previous head and fails with `non-finite difference NaN` now, and so does
the triangular comparison of
`triangular_products_on_the_team_match_sequential_and_stay_on_it`. The complex
triangular product and the `spicy_matmul` comparisons are `x86_64`-only, so
their call sites are converted but cannot be mutated on this host.

`max_diff`'s exception for a non-finite difference is now two equal infinities
rather than equal bits, which admitted two NaNs of the same bits: their
difference is NaN, `f64::max` dropped it, and the measured `max_diff` of a 1x1
NaN against itself was 0.0. Two tests pin both halves: a NaN pair must panic,
two `+inf` must still read as zero.

The bound of `team_agreement_and_actual_row_dispatch` has no floor, and the
complex fixtures are not indifferent to one: their `|want|` is 0.161087, so a
floor of 1 was 6.21 times the bound. A 3e-11 relative error on those fixtures
passes with the floor and fails against the bound as it stands.

`tests/lq_no_alloc.rs` records where its one failure comes from: on a host whose
`gemm` selects the aarch64 SME kernel, `PackBuffer::take` builds a `MemBuffer`
on every call. That is a dependency path, not this machine's allocator, and it
reproduces identically at `620b96a`.
@shinaoka

shinaoka commented Nov 9, 2026

Copy link
Copy Markdown
Member Author

GPT-6.1-sol adversarial review

CRITICAL: none
NEEDS_CHANGES: 1
UNVERIFIED: 1

Critical (must not merge)

none

Needs changes

  • faer/tests/pfaffian.rs:401-402 — The comment still confuses the bound multiplier with the relative error admitted by the floor. The floor makes the bound approximately 3.2569e20 times looser for both real fixtures, not 3.7e9 for f64 and 2.0e18 for f32; those latter numbers are floor_bound / |want|. Reproduce with python3 -c 'w=3.0703979537356053e-21; f=2**-52*200*258; print(f/(f*w), f/w)', which prints approximately 3.2569e20 and 3.7316e9. Correct the source comment to match the distinction already made in the PR body's table.

Unverified claims

  • faer/src/utils/team.rs:1993,2022,2237,2299; .github/workflows/run-tests.yml:122-123 — Exact-head hosted validation of the changed x86_64-only comparisons remains unverified. The successful runs listed in the body belong to ce7ec195, not reviewed head 1c569a0; when checked, the current head's build and testing-stable-ubuntu-latest jobs were still pending. Reproduce with gh pr checks 334 and gh run view 42563053613 --json headSha,status,conclusion,jobs. The local aarch64 run does not execute these x86_64-only tests, so the older green runs cannot establish their behavior after the final helper changes.

Checked and fine

Re-ran the full documented release command at 1c569a0: 32 targets, 952 passed, 1 failed, 163 ignored; the failure reproduced [13, 12, 17, 17] in lq_allocates_nothing_60x300.
Re-ran the focused matmul, Schur, and team tests; reproduced all 16 printed Pfaffian differences as zero.
Not checked: historical mutation runs or local x86_64 execution; the full local suite remains red.


Reviewed head: 1c569a0423fe12b7baebb8bb40c2c491c431e2b9 at 2026-11-09T04:42:52Z. This review is valid only for that commit: if the head has moved, it is a review of a tree that is not being merged.
Automated (gpt_review.sh). This is an enumeration of counterexamples, not a verdict; the merge decision stays with a human.

The comment gave two different quantities as if they were one: `3.7e9` (f64)
and `2.0e18` (f32) are what the floored bound admits as a *relative error*,
while `6.21` on the complex fixtures is the ratio of the two bounds. A floor of
1 makes the bound `1 / |want|` times looser, which is `3.2569e20` for `|want| =
3.07e-21` and `6.2078` for `|want| = 0.161087`; the relative-error numbers stay
in the PR body's table, where they have their own column.
@shinaoka

shinaoka commented Nov 9, 2026

Copy link
Copy Markdown
Member Author

GPT-6.1-sol adversarial review

CRITICAL: none
NEEDS_CHANGES: 1
UNVERIFIED: 3

Critical (must not merge)

none

Needs changes

  • The new NaN-rejection helper has no committed regression test — faer/src/utils/team.rs:865-867: the NaN test at :891-895 calls max_diff, not max_finite, so it does not protect the guard added to the manual comparisons. Reproduction: in a scratch checkout, remove assert!(x.is_finite(), ...), then fold [f64::NAN] with max_finite; it returns 0.0, restoring the false-pass behavior at :2079-2080. The PR’s temporary NaN injections were reverted, leaving no committed test that exercises this rejection. Add a #[should_panic(expected = "non-finite difference")] test calling max_finite(0.0, f64::NAN). This mutation was not rerun during this review.

Unverified claims

  • Current-head numerical CI has not finished — .github/workflows/run-tests.yml:123-124: GitHub reports run 42564277637, for the reviewed head d85cac2adea56678b42fb9e52d640653d9dd0d58, as in_progress, with “Collect coverage data” unfinished. Reproduce with gh api repos/tensor4all/faer/actions/runs/42564277637/jobs. The cited successful run 42556847656 belongs to ce7ec195, not this head; it cannot establish that the final x86_64 numerical suite passes.

  • The allocation counts are recorded, but the asserted SME attribution is not independently established by the available artifacts — faer/tests/lq_no_alloc.rs:17-24: /private/tmp/faer334-review-suite.log contains the reported [13, 12, 17, 17] failure, but I cannot read the claimed base-run and allocator-backtrace artifacts from the supplied evidence. Reproduction requires running cargo test -p t4a-faer --release --test lq_no_alloc at both revisions on the stated SME-capable host and capturing the first counted allocation’s stack. The current-run log alone does not establish the exact caller or the base-versus-head comparison.

  • Fixed-width bitwise reproducibility across processes remains unverified — faer/src/lib.rs:183-188, docs/reproducibility.md:7-19: the linked document explicitly acknowledges the missing second-process check. Reproduction requires computing the same nontrivial input in two fresh processes at one pinned width and comparing serialized component to_bits() values. The changed tolerance checks and the matmul == comparison do not establish this property; the PR acknowledges that limitation.

Checked and fine

  • Recalculated the Pfaffian bounds, floor ratios, and admitted relative errors from the supplied fixture magnitudes.
  • The recorded suite’s result blocks total 952 passed, 1 failed, 163 ignored, matching the PR body.
  • Not checked: fresh Cargo or mutation executions, allocator backtraces, or process-restart reproducibility; current-head numerical CI remains pending.

Reviewed head: d85cac2adea56678b42fb9e52d640653d9dd0d58 at 2026-11-09T05:07:00Z. This review is valid only for that commit: if the head has moved, it is a review of a tree that is not being merged.
Automated (gpt_review.sh). This is an enumeration of counterexamples, not a verdict; the merge decision stays with a human.

The NaN tests added with `max_diff` cover that helper, not `max_finite`, so
removing the guard from the manual comparisons left no committed test red:
`max_finite(0.0, f64::NAN)` then returns `0.0`, which is the false pass the
guard exists to prevent. This adds the direct case, `should_panic` on
"non-finite difference"; deleting the `assert!` from `max_finite` makes it fail
with "test did not panic as expected".
@shinaoka

shinaoka commented Nov 9, 2026

Copy link
Copy Markdown
Member Author

GPT-6.1-sol adversarial review

CRITICAL: none
NEEDS_CHANGES: 1
UNVERIFIED: 2

Critical (must not merge)

none

Needs changes

  • faer/tests/lq_no_alloc.rs:18–24 — The allocation explanation incorrectly describes PackBuffer::take as allocating on every call. The pinned dependency is t4a-gemm-common 0.20.4 (Cargo.lock:2709–2712); its src/gemm.rs:83–94 first tries a thread-local cached buffer, and :114–129 retains buffers up to its L2-sized cap. Source-level reproduction: call PackBuffer::take with a small request, drop it, then repeat the same request on the same thread; the second call returns through can_hold(req) before reaching MemBuffer::new. SME dispatch does not disable this cache. Narrow the comment to the observed 60 × 300 pipeline and identify why its requests miss the cache or exceed the retention cap; an allocation backtrace alone does not establish the stated “every call” mechanism.

Unverified claims

  • faer/src/lib.rs:183–188 — Fixed-width bitwise reproducibility across fresh processes remains unverified. The linked contract includes fresh processes, but docs/reproducibility.md:15–19 explicitly identifies the missing check, and the modified tests do not supply it. Reproduction procedure: run an identical fixture in two fresh processes with the same explicit Par width and compare every output component’s to_bits(); numerical equality or a zero arithmetic difference does not establish this property.

  • .github/workflows/run-tests.yml:123–124 — Full numerical verification of the submitted head is not established by the cited hosted runs. Run 42556847656 belongs to ce7ec195, not reviewed head 242f7628; when checked, the corresponding final-head run 42566728531 was still in_progress. Reproduce with gh run view 42566728531 --repo tensor4all/faer --json headSha,status,conclusion. Also, the successful final-head quality job is not evidence that formatting or faer linting passed: its raw log records exit codes 1 and 101, respectively, which .github/workflows/code-quality.yml:32,45 permits through continue-on-error.

Checked and fine

  • At 242f7628, focused checks passed: three comparison-helper tests, the matmul team test, the Pfaffian team test, and the Schur team test; all 16 printed Pfaffian differences were zero.
  • Independently recalculated the Pfaffian bound ratios and matmul maximum entry, 101, including the shrunken maximum, 1.01e-18.
  • Not rerun: the full suite, mutation experiments, allocation attribution, or cross-process comparison; those results remain outside this review’s execution evidence.

Reviewed head: 242f7628f6f9862aea5cc0538dde4358c077707f at 2026-11-09T05:28:07Z. This review is valid only for that commit: if the head has moved, it is a review of a tree that is not being merged.
Automated (gpt_review.sh). This is an enumeration of counterexamples, not a verdict; the merge decision stays with a human.

`t4a-gemm-common 0.20.4` keeps the pack buffer of the last call in a
thread-local slot and its `Drop` keeps a buffer only up to
`CACHE_INFO[1].cache_bytes` (`src/gemm.rs:84-97`, `:114-129`), so "a fresh
`MemBuffer` on every call" was wrong about the mechanism.

The counted window's allocation sizes say which of those two conditions fires:
3993600 and 4390912 B for f64 (and 7895040, 8781824 B for f32) are above the
2796202 B the crate reports as the cap on this host, so each pass leaves the slot
empty, and each smaller request that follows is larger than the buffer then in
the slot. That is the 13 allocations; the counts are unchanged by this commit.
The `x86_64` half of the sentence stays: that host does not dispatch to the SME
kernel.
@shinaoka

shinaoka commented Nov 9, 2026

Copy link
Copy Markdown
Member Author

GPT-6.1-sol adversarial review

CRITICAL: none
NEEDS_CHANGES: 2
UNVERIFIED: 2

Critical (must not merge)

none

Needs changes

  • An infinite QR sentinel still makes a tolerance comparison vacuous — faer/src/utils/team.rs:879, faer/src/utils/team.rs:1095-1097. The updated max_diff accepts matching infinities, but its caller includes those infinities in the reference scale, so arbitrarily large finite errors pass against an infinite bound; this is an inherited hole remaining in the comparison this PR hardens.

    Reproduce: Use the column-pivoting QR fixture at :1117-1125, clone its Householder factor h, and add 1e6 to the clone’s (0,0) coefficient while retaining the infinity sentinel. Apply the existing max_diff and tolerance expressions. A standalone probe using the existing release library produced:

    infinity at (7,199)
    block_size=32 max_diff=1000000 scale=inf accepted=true
    

    The probe is at /tmp/faer-334-review.rkpShz/probe.rs. Exclude infinity sentinels from the scale calculation while retaining the explicit sentinel-agreement check, and add a regression that rejects this corrupted coefficient.

  • The new explanation incorrectly excludes count-dependent partitioning — faer/src/lib.rs:184-185. Saying the cost model splits work “by the problem rather than by the count” contradicts the count-dependent partition still implemented in faer/src/linalg/evd/tridiag.rs:392-397 and described in the linked reproducibility document.

    Reproduce: Evaluate that function’s boundary expression, floor((1 - sqrt(1 - idx/nthreads)) * n2), with n2 = 8: two participants produce [0, 2, 8], while four produce [0, 1, 2, 4, 8]. The problem size is unchanged; the count changes the partition. Replace the causal sentence with wording that allows both execution-path and partition changes when the requested width changes.

Unverified claims

  • Current-head hosted numerical validation is not established by the cited greens — .github/workflows/run-tests.yml:123-124. I verified that cited run 42556847656 succeeded on ce7ec195, not reviewed head 0136bca7; at inspection, current-head run 42570326466 still had testing-stable-ubuntu-latest in progress in “Collect coverage data.” Reproduce with:

    gh run view 42556847656 --repo tensor4all/faer --json headSha,conclusion
    gh run view 42570326466 --repo tensor4all/faer --json headSha,status,conclusion,jobs

    The body labels the older revision explicitly, but those results cannot establish a completed numerical pass for this head.

  • The detailed allocation measurements cannot be independently checked from the supplied evidence — faer/tests/lq_no_alloc.rs:23-29. The dependency source establishes the reuse and eviction policy, but not the measured 2796202-byte cap, allocation-size sequence, or identical base/head counts; I cannot read a linked raw allocation trace supporting those numbers. Reproduce the count comparison with cargo test -p t4a-faer --release --test lq_no_alloc at both revisions on the stated SME-capable host; validating the size-by-size explanation additionally requires the instrumented allocator trace.

Checked and fine

Seven focused tests passed using existing release binaries, not rebuilt libraries; the Pfaffian run reproduced all sixteen reported zero differences.
The supplied Pfaffian magnitudes reproduce the stated floor ratios; the matmul fixture’s maximum absolute entry is 101.

Not checked: a rebuilt full suite or the historical allocation traces. kache doctor --verify timed out before completion; its store verification remains incomplete.


Reviewed head: 0136bca712e9a60bb5545dbbc93391caeefbda34 at 2026-11-09T06:07:30Z. This review is valid only for that commit: if the head has moved, it is a review of a tree that is not being merged.
Automated (gpt_review.sh). This is an enumeration of counterexamples, not a verdict; the merge decision stays with a human.

shinaoka and others added 2 commits November 9, 2026 16:14
The families test scaled its tolerance by the largest |a| of the reference,
and the 32x32 QR fixture holds +inf, so 1e-10 * scale was infinite and any
finite error passed. Only finite entries now enter the scale; max_diff still
requires an infinity to match on both sides. A regression test compares the
fixture's q against a copy whose (0, 0) is off by 1e6 and expects a panic.

lib.rs: the width can change both the path a call takes and how work is
partitioned (tridiag splits by the participant count), so the old wording
"by the problem rather than by the count" contradicted tridiag.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review reproduced the hole on the families test's own column pivoting
QR, whose h holds an infinity sentinel. Share that fixture as col_piv_qr and
add a regression that rejects h against a copy with (0, 0) off by 1e6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shinaoka

shinaoka commented Nov 9, 2026

Copy link
Copy Markdown
Member Author

GPT-6.1-sol adversarial review

CRITICAL: none
NEEDS_CHANGES: 1
UNVERIFIED: 1

Critical (must not merge)

none

Needs changes

  • The zero-reference comment contradicts the code — faer/src/linalg/matmul/mod.rs:2482-2487: the comment says an all-zero reference produces a zero bound and demands exact equality, but assert!(scale > 0.0) rejects it before comparison. Reproduction: change the lhs fixture to Mat::<f64>::zeros(130, 70) and run cargo test -p t4a-faer --release --features threadteam --lib test_matmul_with_conj_uninit_team; the reference is zero, so that assertion necessarily panics even when both products are correct. Remove the zero-reference claim or make the helper accept zero scale.

Unverified claims

  • The cited green numerical run does not validate the reviewed head — faer/src/utils/team.rs:946-968, .github/workflows/run-tests.yml:123-125: run 42570326466 succeeded on 0136bca712e9a60bb5545dbbc93391caeefbda34, before the two new tolerance regressions. At inspection, numerical run 42578313512 and build run 42578313516 both targeted 403f180564dc43617a7088db7aa8f5a01e454de0 but remained in_progress; the numerical job’s coverage/test step was still running. Reproduce with gh api repos/tensor4all/faer/actions/runs/42578313512 and its /jobs endpoint. This is a disclosed verification gap, not evidence that the new tests fail.

Checked and fine

Recalculated the Pfaffian table’s bounds and ratios; independently calculated the matmul fixture’s largest absolute entry as 101.

Not checked: Rust tests and mutation tests were not rerun during this review; current-head numerical CI was incomplete when inspected.


Reviewed head: 403f180564dc43617a7088db7aa8f5a01e454de0 at 2026-11-09T07:25:11Z. This review is valid only for that commit: if the head has moved, it is a review of a tree that is not being merged.
Automated (gpt_review.sh). This is an enumeration of counterexamples, not a verdict; the merge decision stays with a human.

The comment said an all-zero reference leaves a zero bound demanding exact
equality, but `assert!(scale > 0.0)` rejects such a reference before any
comparison. Say that instead (GPT review of 403f180).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shinaoka

shinaoka commented Nov 9, 2026

Copy link
Copy Markdown
Member Author

GPT-6.1-sol adversarial review

CRITICAL: none
NEEDS_CHANGES: 1
UNVERIFIED: 2

Critical (must not merge)

none

Needs changes

  • The new Schur comparisons accept an infinite sequential reference against a finite team result — faer/tests/generalized_schur_reorder.rs:558-561 switches to close, whose calculation at faer/tests/common/mod.rs:24-26 accepts error = inf against bound = inf, so a serial-only overflow regression can now pass where the removed assert_eq! failed.
    • Reproduction: Immediately after the sequential reorder(...).unwrap() and before the thread-count loop, inject s[(0, 0)] = from_f64::<T>(f64::INFINITY), then run cargo test -p t4a-faer --release --features threadteam --test generalized_schur_reorder sequential_and_team_agree; the newly selected comparator accepts the corrupted reference.
    • Verified evidence: A standalone probe invoking the exact head’s unchanged common::close on an 8×8 identity against a copy with (0, 0) = +inf printed error=inf, bound=inf and returned successfully; this used an existing local faer library, not a rebuilt head.
    • Required change: Reject non-finite norms before comparing them, and add a regression asserting that a finite matrix versus an infinite reference fails.

Unverified claims

  • Completed numerical coverage of the reviewed head remains unverified — .github/workflows/run-tests.yml:128-129: when inspected, run 42580569494 for 7f6afedd39889e4a1cb6128d789d8f826a147c54 still had testing-stable-ubuntu-latest executing “Collect coverage data,” and build run 42580569340 was also in progress; the successful runs cited for 0136bca do not establish completion on this head. Recheck with gh api repos/tensor4all/faer/commits/7f6afedd39889e4a1cb6128d789d8f826a147c54/check-runs.

  • The allocation counts and host-specific explanation are not independently established by the committed change — faer/tests/lq_no_alloc.rs:18-30 records the cache cap, buffer sizes, and [13, 12, 17, 17] counts, but the complete original allocation traces and scratch cache-cap program are uncommitted and cannot read from this revision. Reproducing the claim requires running the documented allocation probe on the stated host at both base and head; the supplied excerpts do not independently demonstrate the complete f32/c64/c32 accounting.

Checked and fine

none

Not checked: the full head suite or the complete mutation suite; the comparator probe above is narrower evidence.
Cache verification after repair completed: 64,291 valid entries, zero corruption; stale GCC-shim warnings remain.


Reviewed head: 7f6afedd39889e4a1cb6128d789d8f826a147c54 at 2026-11-09T07:40:25Z. This review is valid only for that commit: if the head has moved, it is a review of a tree that is not being merged.
Automated (gpt_review.sh). This is an enumeration of counterexamples, not a verdict; the merge decision stays with a human.

With an infinity on one side, `close` computed error = inf and bound = inf,
and `inf <= inf` accepted the pair, so team_case's comparisons (which replaced
assert_eq!) could pass a sequential-only overflow. `close` now requires both
to be finite; close_rejects_an_infinite_reference pins it (GPT review of
7f6afed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shinaoka

shinaoka commented Nov 9, 2026

Copy link
Copy Markdown
Member Author

GPT-6.1-sol adversarial review

CRITICAL: none
NEEDS_CHANGES: 0
UNVERIFIED: 2

Critical (must not merge)

none

Needs changes

none

Unverified claims

  • Completed numerical CI on the reviewed head is not established — .github/workflows/run-tests.yml:123-124: run 42581801235 targets 6ae77d260530f09cb7dc7a210737771d50b021a9, but its numerical coverage step remains in progress; build run 42581802705 also remains in progress. Reproduce with gh pr checks 334 --repo tensor4all/faer. The successful runs cited for 0136bca do not verify this head; the successful MSRV jobs execute cargo check, not the numerical tests.

  • The detailed allocation attribution remains partially unverified — faer/tests/lq_no_alloc.rs:18-30: I reproduced [13, 12, 17, 17] at both 620b96a and 6ae77d2 using cargo test -j 4 -p t4a-faer --release --test lq_no_alloc lq_allocates_nothing_60x300 -- --test-threads=1, and read the dependency’s buffer-reuse and retention conditions, but cannot read the reverted allocation probes or their original traces. These runs establish the inherited failure, not the asserted allocation sizes, SME call stack, or measured 2796202-byte cap; those require replaying the probes documented in the PR.

Checked and fine

  • At 6ae77d2: 44 Schur/Pfaffian tests, 39 other shared-helper tests, 34 team tests, and the matmul team test passed; all 16 printed Pfaffian differences and the tolerance-table arithmetic matched.
  • The changed runtime-source hunks are documentation or test-only code; no production implementation or public signature change was found.
  • kache doctor --repair --verify: 64,304 valid entries, zero corrupted; stale GCC shim warnings remain.

Not checked: the full suite, mutation replay, or local x86-only execution; completed current-head hosted numerical coverage remains unverified.


Reviewed head: 6ae77d260530f09cb7dc7a210737771d50b021a9 at 2026-11-09T07:51:36Z. This review is valid only for that commit: if the head has moved, it is a review of a tree that is not being merged.
Automated (gpt_review.sh). This is an enumeration of counterexamples, not a verdict; the merge decision stays with a human.

@shinaoka
shinaoka merged commit caf3f49 into main Nov 9, 2026
9 checks passed
@shinaoka
shinaoka deleted the docs/327-thread-count-reproducibility branch November 9, 2026 08:19
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.

Contract: bitwise reproducibility is promised at a fixed thread count only (cross-thread requirement withdrawn)

1 participant