Skip to content

perf(linalg): specialized tridiagonal eigensolver for Lanczos - #459

Merged
ultimatile merged 1 commit into
mainfrom
perf/191-tridiag-eigh
Jul 10, 2026
Merged

perf(linalg): specialized tridiagonal eigensolver for Lanczos#459
ultimatile merged 1 commit into
mainfrom
perf/191-tridiag-eigh

Conversation

@ultimatile

Copy link
Copy Markdown
Owner

Summary

lanczos_smallest solved its per-iteration tridiagonal eigenproblem by materializing the m-by-m dense matrix and calling eigh, paying the dense path's O(m^3) tridiagonalization for a matrix that is already tridiagonal. This PR adds a specialized real-symmetric-tridiagonal eigensolver tridiag_eigh to the backend-dispatch stack, backed by faer's public tridiagonal_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 (default NotSupported), and par_for_tridiag_eigh (default Sequential; 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 wrapping faer::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_backend and expert::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. No DenseHostOps method: the inputs are slices with no tensor receiver, documented alongside the existing einsum_with_backend exception.
  • Delegating wrapper backends (AltHostBackend, RecordingBackend, RowMajorBackend) forward the new kernel and policy hook.
  • ariadnetor-algorithms/src/krylov/lanczos_kernels.rs: solve_tridiagonal_smallest drops the dense build and the m == 1 special case, and extracts the eigenvector column through the order-aware accessor instead of assuming a memory order.
  • Makefile.toml: the bench-linalg regression task now runs the new tridiag_eigh bench and the previously omitted reorder_sandwich bench, 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 eigh users operate on genuinely dense matrices and are unaffected. The public API additions are purely additive.

Test plan

  • New contract tests in 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 dense eigh oracle, 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.
  • The existing Lanczos suite (eigenvalue / residual / converged / near-degenerate cluster / n = 1 / non-finite) passes unchanged as the caller-contract regression gate.
  • Full workspace tests, clippy (all targets), and the pluggability-litmus feature configuration (cargo make litmus, the alternate-Host substrate check) are green.
  • Perf (per call, f64, sequential, Apple Silicon; criterion bench included in the PR): dense eigh vs tridiag_eigh is 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

  • Re-solving the tridiagonal problem only every k Lanczos iterations (the interim optimization Specialized symmetric-tridiagonal eigensolver for Lanczos #191 also describes) is an independent follow-up and is not included here.
  • par_for_tridiag_eigh stays Sequential until a workload demonstrates parallel demand; the expert entry point already accepts an explicit policy.

@coderabbitai ignore

…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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and par_for_tridiag_eigh to the backend API surface.
  • Implements native f64/f32 kernels via faer::linalg::evd::tridiagonal_self_adjoint_evd with 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.

@ultimatile
ultimatile merged commit c42e8a9 into main Jul 10, 2026
1 check passed
@ultimatile
ultimatile deleted the perf/191-tridiag-eigh branch July 11, 2026 07:14
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.

Specialized symmetric-tridiagonal eigensolver for Lanczos

2 participants