Skip to content

Commit ec7e1df

Browse files
Merge pull request #368 from KernelTuner/llamea_algs
Added the best generated LLaMEA algorithms
2 parents 65a4e4b + 63abbdf commit ec7e1df

4 files changed

Lines changed: 483 additions & 2 deletions

File tree

kernel_tuner/interface.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@
6565
pyatf_strategies,
6666
random_sample,
6767
simulated_annealing,
68-
skopt
68+
skopt,
69+
gen_hybrid_vndx,
70+
gen_adaptive_tabu_greywolf,
6971
)
7072
from kernel_tuner.strategies.wrapper import OptAlgWrapper
7173

@@ -87,6 +89,8 @@
8789
"firefly_algorithm": firefly_algorithm,
8890
"bayes_opt": bayes_opt,
8991
"pyatf_strategies": pyatf_strategies,
92+
"hybrid_vndx": gen_hybrid_vndx,
93+
"adaptive_tabu_greywolf": gen_adaptive_tabu_greywolf,
9094
}
9195

9296

@@ -397,6 +401,8 @@ def __deepcopy__(self, _):
397401
* "random_sample" takes a random sample of the search space
398402
* "simulated_annealing" simulated annealing strategy
399403
* "skopt" uses the minimization methods from `skopt`
404+
* "HybridVNDX" a hybrid variable neighborhood descent strategy
405+
* "AdaptiveTabuGreyWolf" an adaptive tabu-guided grey wolf optimization strategy
400406
401407
Strategy-specific parameters and options are explained under strategy_options.
402408
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
"""
2+
Adaptive Tabu-Guided Grey Wolf Optimization.
3+
4+
Algorithm generated as part of the paper "Automated Algorithm Design For Auto-Tuning Optimizers".
5+
"""
6+
7+
import random
8+
import math
9+
from collections import deque
10+
11+
from kernel_tuner.util import StopCriterionReached
12+
from kernel_tuner.strategies import common
13+
from kernel_tuner.strategies.common import CostFunc
14+
15+
16+
_options = dict(
17+
budget=("maximum number of evaluations", 5000),
18+
pack_size=("number of wolves", 8),
19+
tabu_factor=("tabu size multiplier", 3),
20+
shake_rate=("base shaking probability", 0.2),
21+
jump_rate=("random jump probability", 0.15),
22+
stagn_limit=("stagnation limit before restart", 80),
23+
restart_ratio=("fraction of pack to restart", 0.3),
24+
t0=("initial temperature", 1.0),
25+
t_decay=("temperature decay rate", 5.0),
26+
t_min=("minimum temperature", 1e-4),
27+
constraint_aware=("constraint-aware optimization (True/False)", True),
28+
)
29+
30+
31+
def tune(searchspace, runner, tuning_options):
32+
33+
options = tuning_options.strategy_options
34+
if "x0" in options:
35+
raise ValueError("Starting point (x0) is not supported for AdaptiveTabuGreyWolf strategy.")
36+
(budget, pack_size, tabu_factor, shake_rate, jump_rate,
37+
stagn_limit, restart_ratio, t0, t_decay, t_min, constraint_aware) = \
38+
common.get_options(options, _options)
39+
40+
cost_func = CostFunc(searchspace, tuning_options, runner)
41+
42+
alg = AdaptiveTabuGreyWolf(
43+
searchspace, cost_func,
44+
budget, pack_size, tabu_factor,
45+
shake_rate, jump_rate,
46+
stagn_limit, restart_ratio,
47+
t0, t_decay, t_min,
48+
constraint_aware,
49+
tuning_options.verbose,
50+
)
51+
52+
try:
53+
alg.run()
54+
except StopCriterionReached as e:
55+
if tuning_options.verbose:
56+
print(e)
57+
58+
return cost_func.results
59+
60+
61+
tune.__doc__ = common.get_strategy_docstring("Adaptive Tabu Grey Wolf", _options)
62+
63+
64+
class AdaptiveTabuGreyWolf:
65+
66+
def __init__(self, searchspace, cost_func,
67+
budget, pack_size, tabu_factor,
68+
shake_rate, jump_rate,
69+
stagn_limit, restart_ratio,
70+
t0, t_decay, t_min,
71+
constraint_aware, verbose):
72+
73+
self.searchspace = searchspace
74+
self.cost_func = cost_func
75+
self.budget = budget
76+
self.pack_size = pack_size
77+
self.tabu = deque(maxlen=pack_size * tabu_factor)
78+
self.shake_rate = shake_rate
79+
self.jump_rate = jump_rate
80+
self.stagn_limit = stagn_limit
81+
self.restart_ratio = restart_ratio
82+
self.t0 = t0
83+
self.t_decay = t_decay
84+
self.t_min = t_min
85+
self.constraint_aware = constraint_aware
86+
self.verbose = verbose
87+
88+
def evaluate(self, dna):
89+
return self.cost_func(dna, check_restrictions=not self.constraint_aware)
90+
91+
def sample_valid(self):
92+
while True:
93+
x = list(self.searchspace.get_random_sample(1)[0])
94+
if not self.constraint_aware or self.searchspace.is_param_config_valid(tuple(x)):
95+
return x
96+
97+
def repair(self, sol):
98+
if not self.constraint_aware or self.searchspace.is_param_config_valid(tuple(sol)):
99+
return sol
100+
101+
# try neighbors
102+
for m in ("adjacent", "Hamming", "strictly-adjacent"):
103+
for nb in self.searchspace.get_neighbors(tuple(sol), neighbor_method=m):
104+
if self.searchspace.is_param_config_valid(nb):
105+
return list(nb)
106+
107+
return self.sample_valid()
108+
109+
def run(self):
110+
111+
# initialize pack
112+
pack = []
113+
num_evals = 0
114+
115+
for cfg in self.searchspace.get_random_sample(self.pack_size):
116+
sol = list(cfg)
117+
118+
try:
119+
val = self.evaluate(sol)
120+
num_evals += 1
121+
except StopCriterionReached:
122+
raise
123+
124+
pack.append((sol, val))
125+
self.tabu.append(tuple(sol))
126+
127+
pack.sort(key=lambda x: x[1])
128+
129+
best_sol, best_val = pack[0]
130+
stagn = 0
131+
iteration = 0
132+
133+
while num_evals < self.budget:
134+
135+
iteration += 1
136+
frac = num_evals / self.budget
137+
138+
# temperature schedule
139+
T = max(self.t_min, self.t0 * math.exp(-self.t_decay * frac))
140+
141+
# reheating
142+
if stagn and stagn % max(1, (self.stagn_limit // 2)) == 0:
143+
T += self.t0 * 0.2
144+
145+
# adaptive shaking
146+
shake_p = min(0.5, self.shake_rate * (1 + stagn / self.stagn_limit))
147+
148+
pack.sort(key=lambda x: x[1])
149+
alpha, beta, delta = pack[0][0], pack[1][0], pack[2][0]
150+
151+
new_pack = []
152+
153+
for sol, sol_val in pack:
154+
155+
# leaders survive
156+
if sol in (alpha, beta, delta):
157+
new_pack.append((sol, sol_val))
158+
continue
159+
160+
D = len(sol)
161+
162+
# recombination
163+
child = [
164+
random.choice((alpha[i], beta[i], delta[i], sol[i]))
165+
for i in range(D)
166+
]
167+
168+
# shaking
169+
if random.random() < shake_p:
170+
if random.random() < self.jump_rate:
171+
idx = random.randrange(D)
172+
rnd = random.choice(self.searchspace.get_random_sample(1))
173+
child[idx] = rnd[idx]
174+
else:
175+
method = "adjacent" if frac < 0.5 else "strictly-adjacent"
176+
nbrs = list(self.searchspace.get_neighbors(tuple(child), neighbor_method=method))
177+
if nbrs:
178+
child = list(random.choice(nbrs))
179+
180+
# repair
181+
child = self.repair(child)
182+
tchild = tuple(child)
183+
184+
# tabu handling
185+
if tchild in self.tabu:
186+
nbrs = list(self.searchspace.get_neighbors(tchild, neighbor_method="Hamming"))
187+
if nbrs:
188+
child = list(random.choice(nbrs))
189+
190+
try:
191+
fch = self.evaluate(child)
192+
num_evals += 1
193+
except StopCriterionReached:
194+
raise
195+
196+
self.tabu.append(tuple(child))
197+
198+
# SA acceptance
199+
dE = fch - sol_val
200+
if dE <= 0 or random.random() < math.exp(-dE / T):
201+
new_pack.append((child, fch))
202+
else:
203+
new_pack.append((sol, sol_val))
204+
205+
if num_evals >= self.budget:
206+
break
207+
208+
pack = new_pack
209+
pack.sort(key=lambda x: x[1])
210+
211+
# update best
212+
if pack[0][1] < best_val:
213+
best_sol, best_val = pack[0]
214+
stagn = 0
215+
else:
216+
stagn += 1
217+
218+
# restart
219+
if stagn >= self.stagn_limit:
220+
nr = int(math.ceil(self.pack_size * self.restart_ratio))
221+
222+
for i in range(self.pack_size - nr, self.pack_size):
223+
sol = self.sample_valid()
224+
225+
try:
226+
val = self.evaluate(sol)
227+
num_evals += 1
228+
except StopCriterionReached:
229+
raise
230+
231+
pack[i] = (sol, val)
232+
self.tabu.append(tuple(sol))
233+
234+
pack.sort(key=lambda x: x[1])
235+
best_sol, best_val = pack[0]
236+
stagn = 0
237+
238+
if self.verbose and num_evals % 50 == 0:
239+
print(f"Evaluations: {num_evals}, best: {best_val}")

0 commit comments

Comments
 (0)