perf: bring sparse eval (CSR storage + sparse GO DAG) to main#19
Merged
Conversation
… RAM) (#15) ## What Two bit-exact performance optimizations to the ontology layer, following a dense→sparse principle. ### 1. Sparse GO DAG (`graph.py`) Replace the dense boolean adjacency `np.zeros((T, T), bool)` with CSR parent/child index arrays + degree vectors, built from the per-term `adj`/`children` sets already maintained. Reproduces the old `dag.sum(axis=1/0)` and `np.nonzero`/`flatnonzero` slices exactly. ### 2. O(1) deque in `top_sort` Kahn's queue used `list.pop(0)` (O(n) per pop → O(n²)); `collections.deque.popleft()` is O(1). FIFO order identical → `self.order` unchanged. ## Correctness (values preserved — bit-exact) Verified identical output TSVs vs the base across **7 scenarios** (sparse / dense / no_orphans / IA-weighted / NK-plain / large-corpus NK & PK). Repo test suite green (9 passed, 6 xfailed, incl. `test_nk_sparse_matches_dense`). ## Measured - Full GO (24.5k BP terms): ontology build **6.97s → 3.11s** (~2.2×), peak RSS for DAG **312 → 108 MB** (~3×). ## Follow-up (not in this PR) A GO-scale eval profiled at **~11.9 GB peak RSS**, dominated by the dense N×T prediction (`float`) matrix. A contained CSR-native sparse-path design (no dense N×T materialised; dense kernels untouched) is scoped and a synthetic GO-scale corpus + bit-exact harness are ready for a dedicated follow-up.
…1.9→7.0 GB) (#16) Stacked on #15. Shows only the prediction-matrix change. ## What The prediction score matrix was a dense `float` (n_prot × n_terms) — ~20 GB at full-GO scale, almost all zeros. It is now built, propagated and consumed sparse: - `graph.propagate_to_coo()` — sparse-native max-propagation (COO in → propagated COO out), bit-identical to the dense `propagate(mode='max')`. - **parser** — the vectorised path builds a `scipy.sparse` CSR straight from the COO it already computes; legacy fallback densifies then re-sparsifies. No dense N×T on the common path. - **evaluation** — sparse NK/PK kernels pull non-zeros from the CSR via `find()`; dense fallback kernels (`CAFAEVAL_SPARSE=0`) densify only their slice. Ground truth stays dense, so the TP gather is unchanged. ## Correctness (bit-exact) Identical output TSVs across **8 scenarios** (sparse / dense / no_orphans / IA-weighted / NK / PK / large-corpus) and on a **24k-term × 30k-protein** synthetic corpus. Repo tests green (9 passed, 6 xfailed). ## Measured (24k-term × 30k-protein corpus) - Peak RSS **11.9 GB → 7.0 GB (−41%)**; wall-clock unchanged (~53s). ## Remaining Ground-truth / exclude matrices are still dense `bool` N×T (smaller). Sparsifying them is the next increment (needs a CSR-aware TP gather), for a further ~1.4 GB.
frapercan
added a commit
that referenced
this pull request
Jun 25, 2026
Cherry-picks the propagation correctness fix (#20, bdbb103) onto main so PROTEA's git+main pin picks it up. main already has the CSR/sparse-DAG perf (#19); this adds ONLY the prop=fill correctness fix (sparse pushup overshot fill blockers; now bit-identical to dense + upstream, property test included). Metric-changing (tiny, ~0.002 Fmax) but strictly more correct.
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.
Promotes develop's sparse evaluation work to main so the branch PROTEA installs (
@main) actually uses it. main had drifted: it only carried today's docs/CI commits (#17, #18) while the sparse perf landed on develop and was never promoted.Brings to main
perf(eval): predictions stored asscipy.sparseCSR, never materialising the dense N x T matrix (11.9 -> 7.0 GB).perf(graph): sparse CSR GO DAG adjacency + O(1)dequetopological sort (about 3x faster build, ~3x less RAM).Verified (py3.12, the only interpreter the stack uses)
CAFAEVAL_PARITY_PHASE=B pytest tests/diff: 7 passed, 6 xfailed, 0 hard failures (the sparse path stays bit-/ULP-exact vs the dense fallback and the upstream oracle).Conflict resolution
Only README.md conflicted (both branches edited the optimisation table): took develop's rows (they document the C1/C3 sparse kernels) and re-applied the em-dash purge from #17. pyproject auto-merged (
requires-python >=3.12from #18 + the scipy/pyarrow sparse-era deps).Follow-up: bump PROTEA's
poetry.lockcafaeval pin so the platform consumes this.