Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Faster GMM1_lpdf#940

Open
antoine-galataud wants to merge 4 commits into
hyperopt:masterhyperopt/hyperopt:masterfrom
antoine-galataud:faster_GMM1_lpdfantoine-galataud/hyperopt:faster_GMM1_lpdfCopy head branch name to clipboard
Open

Faster GMM1_lpdf#940
antoine-galataud wants to merge 4 commits into
hyperopt:masterhyperopt/hyperopt:masterfrom
antoine-galataud:faster_GMM1_lpdfantoine-galataud/hyperopt:faster_GMM1_lpdfCopy head branch name to clipboard

Conversation

@antoine-galataud

@antoine-galataud antoine-galataud commented Jun 16, 2025

Copy link
Copy Markdown

Hi there,

While running hyperopt in Ray Tune with large number of samples, I noticed that performance was dropping quite fast after a few hundreds of them.

I did some profiling using cProfile and noticed that GMM1_lpdf had a important total time of execution. See below an example of profiling results sorted by total time

image

2 things to notice:

  • GMM1_lpdf total time is large
  • normal_cdf total time is large too, and its number of calls is quite important.

Looking at GMM1_lpdf code, the code block that calls normal_cdf most is the following:

hyperopt/hyperopt/tpe.py

Lines 153 to 166 in 0658f68

prob = np.zeros(samples.shape, dtype="float64")
for w, mu, sigma in zip(weights, mus, sigmas):
if high is None:
ubound = samples + q / 2
else:
ubound = np.minimum(samples + q / 2, high)
if low is None:
lbound = samples - q / 2
else:
lbound = np.maximum(samples - q / 2, low)
# -- two-stage addition is slightly more numerically accurate
inc_amt = w * normal_cdf(ubound, mu, sigma)
inc_amt -= w * normal_cdf(lbound, mu, sigma)
prob += inc_amt

Several observations here:

  • variables lbound and ubound don't depend on for loop parameters
  • calls to normal_cdf depends on length of given arrays, which grows with number of samples.

After analyzing the impact, I drafted a "vectorized" version of the code, which doesn't rely on for loop anymore but executes the computation in one pass.

Tests are passing, and I've been able to validate the results with in-house experiments too.

Feedback appreciated!

@antoine-galataud

Copy link
Copy Markdown
Author

Comparison of performance between the 2 versions of the code:

  • as weights / mus / sigmas length grows, and samples length remains constant and small, the new version of the code becomes more efficient.
  • if samples is large then there's a cut-off length where original version is again more efficient than new one. This is due to operations in normal_cdf on ubound and lbound that become large, especially the call to scipy.special.erf.

While testing with my scenarios, I encountered only case number 1. But that may not cover all use cases.

@github-actions github-actions Bot added the Stale label Oct 16, 2025
@github-actions github-actions Bot closed this Nov 15, 2025
@jaberg jaberg reopened this Jul 20, 2026
@github-actions github-actions Bot removed the Stale label Jul 21, 2026
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.

3 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.