Skip to content

refactor(evals): vectorize correlation metrics - #145

Open
KenyaOtsuka wants to merge 2 commits into
KamitaniLab:devfrom
KenyaOtsuka:refactor/vectorize-evals-metrics
Open

KenyaOtsuka wants to merge 2 commits into
KamitaniLab:devfrom
KenyaOtsuka:refactor/vectorize-evals-metrics

Conversation

@KenyaOtsuka

Copy link
Copy Markdown

Why

profile_correlation, pattern_correlation, and pairwise_identification
become very slow on modern vision features with hundreds of thousands of
units. A 1,492 × 425,984 evaluation had not finished after 17 hours.

What

  • Vectorize profile_correlation and pattern_correlation using blocked
    centered dot products.
  • Use BLAS-backed matrix multiplications for the correlation path of
    pairwise_identification.
  • Process large feature arrays in blocks to bound working memory.
  • Keep non-correlation metrics and single_trial=True on the existing cdist
    path.

For real-valued inputs, public signatures, output shapes, defaults, NaN
handling, and warning behavior are unchanged. The implementation also
preserves the [-1, 1] clipping used by NumPy/SciPy correlation calculations.

Test

Existing golden tests still pass at rtol=1e-12, atol=1e-12.

Added regression tests for zero-variance inputs, NaN handling, mean / std,
the correlation path of pairwise_identification, non-correlation fallback,
and multi-block execution.

Verified with NumPy 1.26.4 / SciPy 1.13.1 and NumPy 2.4.6 / SciPy 1.17.1.

Add tests for behavior that is currently only exercised indirectly through
the pickled 2-d fixtures: undefined correlations on zero-variance columns
and rows, NaN column removal (and its opt-out), the mean/std arguments of
pattern_correlation, and pairwise_identification against a definition-based
oracle built from cdist, including a non-correlation metric.
profile_correlation and pattern_correlation called np.corrcoef once per
unit and per sample, and pairwise_identification went through cdist, whose
correlation metric walks every pair in a Python-level C loop. On the
feature sizes produced by recent vision encoders (hundreds of thousands of
units) this dominates the evaluation runtime.

Compute the same quantities with array operations instead:

- profile_correlation and pattern_correlation accumulate centered dot
  products over blocks of the axis they do not reduce over, which keeps the
  two-pass formula of np.corrcoef, including its clipping to [-1, 1] and
  the NaN it yields for a zero-variance column or row.
- pairwise_identification derives 1 - cdist(p, t, 'correlation') from a
  single matrix product when the metric is 'correlation'. Other metrics and
  the single-trial branch still go through cdist, and the NaN handling that
  follows is untouched, so the emitted warnings are unchanged. scipy clips
  the cosine it builds the correlation distance from, so the matrix product
  is clipped the same way.

Blocking the work also bounds peak memory, which the previous
standardization and NaN scan did not: they materialized full-size copies.

Measured with random arrays of the given (n_samples, n_units), in seconds,
best of three runs:

                            500x5000        50x1000000       1500x40000
                          before  after   before   after   before   after
  profile_correlation      0.17    0.01    28.12    0.30     2.75    0.34
  pattern_correlation      0.03    0.01     0.39    0.35     0.45    0.38
  pairwise_identification  0.45    0.03     3.26    0.64    89.26    1.43

profile_correlation gains as the unit count grows, since it used to make one
np.corrcoef call per unit. pairwise_identification gains as the sample count
grows, since its cost is pairs times units. pattern_correlation gains least,
and at some shapes is only break-even: it already looped over the sample
axis, the small one, so the gain there is in memory. Peak RSS of the
1500x40000 pairwise_identification run drops from 931 MiB to 149 MiB.
@KenyaOtsuka

Copy link
Copy Markdown
Author

Benchmark results, seconds, best of three runs on the same machine:

Metric 500×5,000 50×1,000,000 1,500×40,000
before after before after before after
profile_correlation 0.170.01 28.120.30 2.750.34
pattern_correlation 0.030.01 0.390.35 0.450.38
pairwise_identification 0.450.03 3.260.64 89.261.43

The largest gains are in profile_correlation for large feature dimensions and in pairwise_identification for large sample counts. pattern_correlation is closer to break-even because it already looped over the smaller sample axis.

Peak RSS for pairwise_identification at 1,500×40,000 dropped from 931 MiB to 149 MiB.

@KenyaOtsuka
KenyaOtsuka requested review from HirokiYasuda03, ganow and micchu and removed request for micchu September 18, 2026 04:02
@KenyaOtsuka
KenyaOtsuka marked this pull request as ready for review September 18, 2026 04:03

This branch has not been deployed

No deployments
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