Skip to content

perf: bring sparse eval (CSR storage + sparse GO DAG) to main#19

Merged
frapercan merged 3 commits into
mainfrom
chore/bring-sparse-to-main
Jun 24, 2026
Merged

perf: bring sparse eval (CSR storage + sparse GO DAG) to main#19
frapercan merged 3 commits into
mainfrom
chore/bring-sparse-to-main

Conversation

@frapercan

Copy link
Copy Markdown
Owner

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

Verified (py3.12, the only interpreter the stack uses)

  • ruff + mypy clean.
  • Parity suite 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.12 from #18 + the scipy/pyarrow sparse-era deps).

Follow-up: bump PROTEA's poetry.lock cafaeval pin so the platform consumes this.

… 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.
Merges develop's sparse evaluation improvements (#15 sparse GO DAG + O(1) top_sort,
#16 CSR prediction storage 11.9->7.0 GB) into main so the branch PROTEA installs
(@main) actually uses them.

# Conflicts:
#	README.md
@frapercan frapercan merged commit 52acad7 into main Jun 24, 2026
6 checks passed
@frapercan frapercan deleted the chore/bring-sparse-to-main branch June 24, 2026 17:28
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.
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.

1 participant