anyoptimization / pymoo

NSGA2, NSGA3, R-NSGA3, MOEAD, Genetic Algorithms (GA), Differential Evolution (DE), CMAES, PSO
https://pymoo.org
Apache License 2.0
2.31k stars 399 forks source link

The StarmapParallelization doesn't seem to work #660

Open SanPen opened 1 week ago

SanPen commented 1 week ago

Hi!

I made this example:

import numpy as np
from multiprocessing.pool import ThreadPool
from pymoo.core.problem import StarmapParallelization
from pymoo.optimize import minimize
from pymoo.algorithms.soo.nonconvex.ga import GA
from pymoo.core.problem import ElementwiseProblem

N = 100

class MyProblem(ElementwiseProblem):

    def __init__(self, **kwargs):
        super().__init__(n_var=N, n_obj=1, n_ieq_constr=0, xl=-5, xu=5, **kwargs)

    def _evaluate(self, x, out, *args, **kwargs):
        mat = np.random.random((N, N))
        b = mat @ x

        a = np.pow(b, 2).sum()

        out["F"] = a
        # sleep(1.0)

# the number of threads to be used
n_threads = 20

# initialize the pool
pool = ThreadPool(n_threads)

runner = StarmapParallelization(pool.starmap)

# define the problem by passing the starmap interface of the thread pool
problem = MyProblem(elementwise_runner=runner)

algorithm = GA(
    pop_size=10000,
)

res = minimize(
    problem=problem,
    algorithm=algorithm,
    seed=1,
    n_gen=1000,
    verbose=True
)
print('Threads:', res.exec_time)

pool.close()

When I run it, this is the processing profile:

imagen

It is clearly not running in parallel. Observe that only one processor thread is not idle.

Any idea on what to do to run a GA in parallel?

blankjul commented 4 days ago

I had a look at it and don't believe that it is an parallelization issue in the problem definition. Check out the following code where I am implementing a custom parallelization of the objective function directly in the problem.

import numpy as np
from multiprocessing.pool import ThreadPool
from pymoo.core.problem import StarmapParallelization, Problem
from pymoo.optimize import minimize
from pymoo.algorithms.soo.nonconvex.ga import GA
from pymoo.core.problem import ElementwiseProblem

pool = ThreadPool(8)

N_VAR = 100
POP_SIZE = 5000
N_GEN = 2

def f(x):
    n = len(x)
    mat = np.random.random((n, n))
    b = mat @ x
    return np.power(b, 2).sum()

class MyProblem(ElementwiseProblem):

    def __init__(self, **kwargs):
        super().__init__(n_var=N_VAR, n_obj=1, n_ieq_constr=0, xl=-5, xu=5, **kwargs)

    def _evaluate(self, x, out, *args, **kwargs):
        out["F"] = f(x)

class MyStarmapProblem(Problem):

    def __init__(self, **kwargs):
        super().__init__(n_var=N_VAR, n_obj=1, n_ieq_constr=0, xl=-5, xu=5, **kwargs)

    def _evaluate(self, X, out, *args, **kwargs):
        out["F"] = pool.starmap(f, [[x] for x in X])

########## REGULAR ##########

problem = MyProblem()

algorithm = GA(pop_size=POP_SIZE)

res = minimize(
    problem=problem,
    algorithm=algorithm,
    termination=('n_gen', N_GEN),
    seed=1,
    verbose=True
)
print('Regular:', res.exec_time)

########## THREADS ##########

runner = StarmapParallelization(pool.starmap)

# define the problem by passing the starmap interface of the thread pool
problem = MyProblem(elementwise_runner=runner)

algorithm = GA(pop_size=POP_SIZE)

res = minimize(
    problem=problem,
    algorithm=algorithm,
    termination=('n_gen', N_GEN),
    seed=1,
    verbose=True
)
print('Threads:', res.exec_time)

########## CUSTOM ##########

# define the problem by passing the starmap interface of the thread pool
problem = MyStarmapProblem()

algorithm = GA(pop_size=POP_SIZE)

res = minimize(
    problem=problem,
    algorithm=algorithm,
    termination=('n_gen', N_GEN),
    seed=1,
    verbose=True
)
print('CUSTOM:', res.exec_time)

########## FIN_VARALIZE ##########

pool.close()

which results in

=================================================
n_gen  |  n_eval  |     f_avg     |     f_min    
=================================================
     1 |     5000 |  2.819835E+04 |  4.097037E+03
     2 |    10000 |  8.990107E+03 |  3.782367E+03
Regular: 3.7044739723205566
=================================================
n_gen  |  n_eval  |     f_avg     |     f_min    
=================================================
     1 |     5000 |  2.816409E+04 |  4.395691E+03
     2 |    10000 |  9.014509E+03 |  3.813322E+03
Threads: 3.2561869621276855
=================================================
n_gen  |  n_eval  |     f_avg     |     f_min    
=================================================
     1 |     5000 |  2.822005E+04 |  4.251145E+03
     2 |    10000 |  9.025169E+03 |  3.818071E+03
CUSTOM: 3.685326099395752

This is the behavior you have referred to in your issue.

The algorithm itself however, does not parallelize on threads which will still be the overhead in your case. Because the objective function runs still quite fast.

To prove this we can use a small population with a longer wait time.

from multiprocessing.pool import ThreadPool
from time import sleep

from pymoo.algorithms.soo.nonconvex.ga import GA
from pymoo.core.problem import ElementwiseProblem
from pymoo.core.problem import StarmapParallelization
from pymoo.optimize import minimize

pool = ThreadPool(8)

N_VAR = 100
POP_SIZE = 8
N_GEN = 2

class MyProblem(ElementwiseProblem):

    def __init__(self, **kwargs):
        super().__init__(n_var=N_VAR, n_obj=1, n_ieq_constr=0, xl=-5, xu=5, **kwargs)

    def _evaluate(self, x, out, *args, **kwargs):
        sleep(1)
        out["F"] = 1

########## REGULAR ##########

problem = MyProblem()

algorithm = GA(pop_size=POP_SIZE)

res = minimize(
    problem=problem,
    algorithm=algorithm,
    termination=('n_gen', N_GEN),
    seed=1,
    verbose=True
)
print('Regular:', res.exec_time)

########## THREADS ##########

runner = StarmapParallelization(pool.starmap)

# define the problem by passing the starmap interface of the thread pool
problem = MyProblem(elementwise_runner=runner)

algorithm = GA(pop_size=POP_SIZE)

res = minimize(
    problem=problem,
    algorithm=algorithm,
    termination=('n_gen', N_GEN),
    seed=1,
    verbose=True
)
print('Threads:', res.exec_time)

########## FIN_VARALIZE ##########

pool.close()

and this will clearly show that the problem is parallelized.

=================================================
n_gen  |  n_eval  |     f_avg     |     f_min    
=================================================
     1 |        8 |  1.0000000000 |  1.0000000000
     2 |       16 |  1.0000000000 |  1.0000000000
Regular: 16.23266291618347
=================================================
n_gen  |  n_eval  |     f_avg     |     f_min    
=================================================
     1 |        8 |  1.0000000000 |  1.0000000000
     2 |       16 |  1.0000000000 |  1.0000000000
Threads: 2.02089524269104