perf(linalg): specialized tridiagonal eigensolver for Lanczos - #459
Merged
Conversation
…anczos Route the Lanczos tridiagonal eigenproblem through a new tridiag_eigh backend op backed by faer's tridiagonal_self_adjoint_evd, dropping the dense m-by-m build and the O(m^3) tridiagonalization the eigh path paid per iteration. The kernel writes eigenvalues and eigenvectors directly into the descriptor buffers, and the public entry points bind the scalar to real types at compile time (a general complex symmetric tridiagonal matrix is not Hermitian); the dispatch layer additionally rejects complex descriptors as defense for direct backend callers. Measured against the dense eigh path (f64, sequential): 1.5x at m=64, 1.5x at m=256, 2.0x at m=512. DMRG-scale end-to-end runs are neutral, as expected for the H*v-dominated regime.
There was a problem hiding this comment.
Pull request overview
This PR introduces a backend-dispatched real-symmetric tridiagonal eigensolver (tridiag_eigh) and routes the Lanczos per-iteration tridiagonal solve through it, avoiding the dense eigh path’s unnecessary O(m³) tridiagonalization step.
Changes:
- Adds
TridiagEighDescriptor,OpDesc::TridiagEigh,ComputeBackend::tridiag_eigh, andpar_for_tridiag_eighto the backend API surface. - Implements native f64/f32 kernels via
faer::linalg::evd::tridiagonal_self_adjoint_evdwith explicit argument validation and complex-type rejection. - Adds contract tests + a criterion benchmark for specialized-vs-dense performance, and wires the new bench into the regression bench task.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| Makefile.toml | Extends bench-linalg regression task to include reorder_sandwich and tridiag_eigh. |
| crates/ariadnetor/src/ops.rs | Re-exports TridiagEighResult and tridiag_eigh_with_backend from ariadnetor-linalg. |
| crates/ariadnetor/src/lib.rs | Umbrella re-exports for TridiagEighResult and tridiag_eigh_with_backend. |
| crates/ariadnetor-tensor/src/capability.rs | Updates the pluggability-litmus AltHostBackend to forward tridiag_eigh + policy hook. |
| crates/ariadnetor-native/src/tridiag_eigh.rs | New native real-tridiagonal EVD kernels (f64/f32) backed by faer, with length validation. |
| crates/ariadnetor-native/src/lib.rs | Hooks new op into dispatch, enforces column-major for eigenvectors, rejects complex scalars. |
| crates/ariadnetor-linalg/tests/tridiag_fixtures/mod.rs | Shared deterministic tridiagonal fixtures + dense assembly helper for tests/bench parity. |
| crates/ariadnetor-linalg/tests/tridiag_eigen.rs | Integration contract tests (ordering, oracle eigenvalues, residuals, orthonormality, edge cases). |
| crates/ariadnetor-linalg/src/with_backend/tests/mod.rs | Adds routing/policy-forwarding tests for tridiag_eigh. |
| crates/ariadnetor-linalg/src/with_backend/mod.rs | Adds public tridiag_eigh_with_backend entry point (slice inputs, tensor outputs). |
| crates/ariadnetor-linalg/src/test_util.rs | Extends RecordingBackend/RowMajorBackend scaffolding to include tridiag_eigh. |
| crates/ariadnetor-linalg/src/lib.rs | Documents tridiag_eigh_with_backend as a free-function exception; re-exports result type + entry point. |
| crates/ariadnetor-linalg/src/expert.rs | Adds expert-layer tridiag_eigh with explicit ExecPolicy. |
| crates/ariadnetor-linalg/src/eigen.rs | Introduces result aliases and internal dense/policy implementations for tridiagonal EVD. |
| crates/ariadnetor-linalg/Cargo.toml | Registers new tridiag_eigh criterion bench target. |
| crates/ariadnetor-linalg/benches/tridiag_eigh.rs | New criterion bench comparing specialized vs dense eigh on tridiagonal inputs. |
| crates/ariadnetor-linalg/benches/README.md | Updates regression bench list to include reorder_sandwich and tridiag_eigh. |
| crates/ariadnetor-core/src/backend.rs | Adds descriptor/op/policy hook and ComputeBackend::tridiag_eigh default to core backend API. |
| crates/ariadnetor-algorithms/src/krylov/lanczos.rs | Updates Lanczos bound commentary to reflect tridiag_eigh_with_backend usage. |
| crates/ariadnetor-algorithms/src/krylov/lanczos_kernels.rs | Routes Lanczos tridiagonal solve through tridiag_eigh_with_backend and uses order-aware column extraction. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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.
Summary
lanczos_smallestsolved its per-iteration tridiagonal eigenproblem by materializing the m-by-m dense matrix and callingeigh, paying the dense path's O(m^3) tridiagonalization for a matrix that is already tridiagonal. This PR adds a specialized real-symmetric-tridiagonal eigensolvertridiag_eighto the backend-dispatch stack, backed by faer's publictridiagonal_self_adjoint_evd, and routes the Lanczos solve through it. Closes #191.Changes
ariadnetor-core/src/backend.rs:TridiagEighDescriptor(diagonal + subdiagonal input, ascending eigenvalues, column eigenvectors),OpDesc::TridiagEigh,ComputeBackend::tridiag_eigh(defaultNotSupported), andpar_for_tridiag_eigh(defaultSequential; the dense-eigh thresholds are tuned for O(n^3) work and do not transfer).ariadnetor-native/src/tridiag_eigh.rs(new): f64/f32 kernels wrappingfaer::linalg::evd::tridiagonal_self_adjoint_evd, writing eigenvalues and eigenvectors directly into the descriptor buffers (no intermediate allocation or copy-back); slice lengths validated before faer, which panics on dimension misuse. The c64/c32 dispatch arms reject complex descriptors: a general complex symmetric tridiagonal matrix is not Hermitian.ariadnetor-linalg:tridiag_eigh_with_backendandexpert::tridiag_eigh, both binding the scalar to real types at compile time (T: Scalar<Real = T>holds only for f32/f64); re-exported through the umbrella crate. NoDenseHostOpsmethod: the inputs are slices with no tensor receiver, documented alongside the existingeinsum_with_backendexception.AltHostBackend,RecordingBackend,RowMajorBackend) forward the new kernel and policy hook.ariadnetor-algorithms/src/krylov/lanczos_kernels.rs:solve_tridiagonal_smallestdrops the dense build and them == 1special case, and extracts the eigenvector column through the order-aware accessor instead of assuming a memory order.Makefile.toml: thebench-linalgregression task now runs the newtridiag_eighbench and the previously omittedreorder_sandwichbench, matching the bench README.Impact
The only production caller is the Lanczos kernel; its contract is unchanged (smallest eigenpair, eigenvector defined up to sign). All other
eighusers operate on genuinely dense matrices and are unaffected. The public API additions are purely additive.Test plan
ariadnetor-linalg/tests/tridiag_eigen.rs, sign-independent by design (eigenvector columns are only defined up to sign, and degenerate spectra only up to a subspace rotation): ascending eigenvalues matching the denseeighoracle, per-pair residuals against an independent tridiagonal matvec, orthonormality of the eigenvector matrix, analytic n = 1 / n = 2 cases, a degenerate spectrum, n = 160 (above faer's divide-and-conquer threshold of 128, so both internal code paths are exercised), length-mismatch errors, and complex rejection at the dispatch layer.pluggability-litmusfeature configuration (cargo make litmus, the alternate-Host substrate check) are green.eighvstridiag_eighis 272 us vs 178 us at m = 64 (1.5x), 4.43 ms vs 2.94 ms at m = 256 (1.5x), 19.9 ms vs 10.2 ms at m = 512 (2.0x). The end-to-end DMRG bench is neutral to slightly better (m stays around 20-50 there, where the H*v applies dominate), as the issue anticipated.Notes
par_for_tridiag_eighstaysSequentialuntil a workload demonstrates parallel demand; the expert entry point already accepts an explicit policy.@coderabbitai ignore