PRIMME and Feng have been deleted from the codebase. The tables and discussion of Feng in this document are retained as the empirical basis for the retirement decision (see
context/DECISIONS.md, "SVD algorithm strategy") and to keep the in-memory and backed defaults' rationale reproducible. The Feng C++ paths are no longer available; the benchmark harnesses no longer accept--include-retired.run_svd(..., algorithm="feng")andalgorithm="primme"raiseValueError.
Empirical basis for the SVD auto-selection routing in
_select_svd_algorithm_inmemory and
_select_svd_algorithm_backed. Two
benchmark harnesses feed this document:
tests/benchmark_svd_inmemory_defaults.py— sparse and dense in-memory paths across IRLB, Halko, and (historically) Feng.tests/benchmark_backed_svd_algorithm.py— the same three algorithms on backed (HDF5-streamed) input.
Both harnesses run each (tier, storage, algorithm, trial) combination in a
fresh subprocess so peak RSS is a clean per-case delta.
- Dataset: stratified subsets of
data/adata_agg_Scn4b_OX_fil.h5ad, generated bytests/benchmark_support.generate_all_subsets()and written todata/actionet_benchmark/scale_subset_<tier>.h5ad. Tiers:25k,50k,100k,150k,200kcells;n_vars = 31,728before filtering. - Preprocessing per run:
filter_anndata(min_cells_per_feat=0.01)andnormalize_anndata(target_sum=1e4, log_transform=True, log_base=2). The preprocessing wall time is not counted in the SVD timing. - SVD parameters:
n_components = 30,seed = 42,max_iter = 0(solver default). - Trials: 2 per configuration. Reference algorithm for
sigma_corris algorithm-specific: IRLB for in-memory, Halko for backed. - Accuracy probe: random 500-row sample for the Frobenius reconstruction
error
||A_probe - U D V'||_F / ||A_probe||_F. - Dense in-memory cases capped at
100kcells; larger tiers would exceed the ~50 GB float64 matrix that fits in a typical HPC compute node. - Hardware for the reference run: macOS arm64, single node, portable build.
Raw JSONL, CSVs, and auto-generated reports live under
tests/benchmark_results/svd_backed_full/ and
tests/benchmark_results/svd_inmem_full/.
Wall time (mean of 2 trials, seconds). Ratios are relative to IRLB.
Accuracy: all three algorithms produce singular values with
sigma_corr >= 0.999998 at every tier; reconstruction error agreement
across algorithms is <= 1e-4 in relative Frobenius norm.
Conclusion. IRLB dominates the sparse path by 3-6x across the full tier range while remaining numerically indistinguishable from the alternatives. Default: IRLB.
Wall time (mean of 2 trials, seconds). Ratios are relative to IRLB.
| tier | IRLB (s) | Halko (s) | Feng (s) | Halko / IRLB | Feng / IRLB |
|---|---|---|---|---|---|
| 25k | 2.67 | 1.12 | 1.30 | 0.42 | 0.49 |
| 50k | 5.49 | 2.35 | 2.58 | 0.43 | 0.47 |
| 100k | 15.17 | 9.11 | 9.60 | 0.60 | 0.63 |
Accuracy: sigma_corr >= 0.999998 across all algorithms and tiers.
Conclusion. Halko is the fastest dense in-memory algorithm at every
tier, beating Feng by 5-9% median wall time and IRLB by roughly 2x. The
user-proposed dense = Feng routing is not supported: Feng is
consistently a step behind Halko on our dense benchmark data.
Default: Halko.
Wall time (mean of 2 trials, seconds). Ratios are relative to Halko.
| tier | Halko (s) | IRLB (s) | Feng (s) | IRLB / Halko | Feng / Halko |
|---|---|---|---|---|---|
| 25k | 4.41 | 31.37 | 4.47 | 7.11 | 1.01 |
| 50k | 8.59 | 63.02 | 8.99 | 7.34 | 1.05 |
| 100k | 17.23 | 125.74 | 18.01 | 7.30 | 1.05 |
| 150k | 25.79 | 187.76 | 27.20 | 7.28 | 1.05 |
| 200k | 42.18 | 272.76 | 36.77 | 6.47 | 0.87 |
Accuracy: sigma_corr >= 0.999998 across all algorithms and tiers vs
the Halko reference.
Conclusion. IRLB's iterative refinement inflates I/O passes over
backed matrices to the point of a 6.5-7.3x wall-time penalty vs Halko.
Feng tracks Halko to within ~5% at tiers <=150k and edges out Halko by
~13% at 200k. Halko remains the default: its median wall time still wins
across the tiers benchmarked (17.23s vs Feng 18.01s), the fixed
2*(iters+1) matvec count gives a more predictable I/O cost model at
atlas scale, and Feng's crossover at 200k does not clear the 10%
wall-time threshold we require for switching a documented default.
Default: Halko.
Prerequisites (one-time):
# Generate stratified tier subsets from data/adata_agg_Scn4b_OX_fil.h5ad
python -c "
import sys; sys.path.insert(0, 'tests')
from benchmark_support import generate_all_subsets
generate_all_subsets(tiers=[25000, 50000, 100000, 150000, 200000])
"Backed benchmark:
python tests/benchmark_backed_svd_algorithm.py \
--tiers 25k 50k 100k 150k 200k \
--trials 2 \
--output-dir tests/benchmark_results/svd_backed_fullIn-memory benchmark (dense capped at 100k to fit typical HPC node RAM):
python tests/benchmark_svd_inmemory_defaults.py \
--tiers 25k 50k 100k 150k 200k \
--trials 2 \
--skip-dense-above 100k \
--output-dir tests/benchmark_results/svd_inmem_fullBoth scripts write raw_results.jsonl, a CSV summary, and a markdown
report with an automated recommendation block into the output directory.
