Skip to content

Commit 1f63c7c

Browse files
Merge pull request #313 from KernelTuner/adjacent-neighborhood-change
Add new `Hamming-adjacent` neighborhood method
2 parents 85e4d64 + a8dd501 commit 1f63c7c

3 files changed

Lines changed: 72 additions & 2 deletions

File tree

kernel_tuner/searchspace.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
get_interval,
4040
)
4141

42-
supported_neighbor_methods = ["strictly-adjacent", "adjacent", "Hamming", "closest-param-indices"]
42+
43+
supported_neighbor_methods = ["strictly-adjacent", "adjacent", "Hamming", "Hamming-adjacent", "closest-param-indices"]
4344

4445

4546
class Searchspace:
@@ -65,6 +66,7 @@ def __init__(
6566
strictly-adjacent: differs +1 or -1 parameter index value for each parameter
6667
adjacent: picks closest parameter value in both directions for each parameter
6768
Hamming: any parameter config with 1 different parameter value is a neighbor
69+
Hamming-adjacent: differs by closest parameter value for exactly 1 parameter.
6870
Optionally sort the searchspace by the order in which the parameter values were specified. By default, sort goes from first to last parameter, to reverse this use sort_last_param_first.
6971
Optionally an imported cache can be used instead with `from_cache`, in which case the `tune_params`, `restrictions` and `max_threads` arguments can be set to None, and construction is skipped.
7072
Optionally construction can be deffered to a later time by setting `defer_construction` to True, in which case the searchspace is not built on instantiation (experimental).
@@ -1038,6 +1040,44 @@ def __get_random_neighbor_hamming(self, param_config: tuple) -> tuple:
10381040
return self.get_param_configs_at_indices([i])[0]
10391041
return None
10401042

1043+
def __get_neighbors_indices_hammingadjacent(self, param_config_index: int = None, param_config: tuple = None) -> List[int]:
1044+
"""Get the neighbors using adjacent distance from the parameter configuration (parameter index absolute difference >= 1)."""
1045+
param_config_value_indices = (
1046+
self.get_param_indices(param_config)
1047+
if param_config_index is None
1048+
else self.params_values_indices[param_config_index]
1049+
)
1050+
1051+
# compute boolean mask for all configuration that differ at exactly one parameter (Hamming distance == 1)
1052+
hamming_mask = np.count_nonzero(self.params_values_indices != param_config_value_indices, axis=1) == 1
1053+
1054+
# get the configuration indices of the hamming neighbors
1055+
hamming_indices, = np.nonzero(hamming_mask)
1056+
1057+
# for the hamming neighbors, calculate the difference between parameter value indices
1058+
hamming_values_indices = self.params_values_indices[hamming_mask]
1059+
1060+
# for each parameter get the closest upper and lower parameter (absolute index difference >= 1)
1061+
upper_bound = np.min(
1062+
hamming_values_indices,
1063+
initial=self.get_list_param_indices_numpy_max(),
1064+
axis=0,
1065+
where=hamming_values_indices > param_config_value_indices,
1066+
)
1067+
1068+
lower_bound = np.max(
1069+
hamming_values_indices,
1070+
initial=self.get_list_param_indices_numpy_min(),
1071+
axis=0,
1072+
where=hamming_values_indices < param_config_value_indices,
1073+
)
1074+
1075+
# return mask for adjacent neighbors (each parameter is within bounds)
1076+
adjacent_mask = np.all((lower_bound <= hamming_values_indices) & (hamming_values_indices <= upper_bound), axis=1)
1077+
1078+
# return hamming neighbors that are also adjacent
1079+
return hamming_indices[adjacent_mask]
1080+
10411081
def __get_random_neighbor_adjacent(self, param_config: tuple) -> tuple:
10421082
"""Get an approximately random adjacent neighbor of the parameter configuration."""
10431083
# NOTE: this is not truly random as we only progressively increase the allowed index difference if no neighbors are found, but much faster than generating all neighbors
@@ -1051,6 +1091,7 @@ def __get_random_neighbor_adjacent(self, param_config: tuple) -> tuple:
10511091
if param_config_index is None
10521092
else self.params_values_indices[param_config_index]
10531093
)
1094+
10541095
max_index_difference_per_param = [max(len(self.params_values[p]) - 1 - i, i) for p, i in enumerate(param_config_value_indices)]
10551096

10561097
# calculate the absolute difference between the parameter value indices
@@ -1079,6 +1120,7 @@ def __get_random_neighbor_adjacent(self, param_config: tuple) -> tuple:
10791120
allowed_index_difference += 1
10801121
return None
10811122

1123+
10821124
def __add_to_neighbor_partial_cache(self, param_config: tuple, neighbor_indices: List[int], neighbor_method: str, full_neighbors = False):
10831125
"""Add the neighbor indices to the partial cache using the given parameter configuration."""
10841126
param_config_index = self.get_param_config_index(param_config)
@@ -1164,6 +1206,13 @@ def __build_neighbors_index(self, neighbor_method) -> List[List[int]]:
11641206
# for each parameter configuration, find the neighboring parameter configurations
11651207
if self.params_values_indices is None:
11661208
self.__prepare_neighbors_index()
1209+
1210+
if neighbor_method == "Hamming-adjacent":
1211+
return list(
1212+
self.__get_neighbors_indices_hammingadjacent(param_config_index, param_config)
1213+
for param_config_index, param_config in enumerate(self.list)
1214+
)
1215+
11671216
if neighbor_method == "strictly-adjacent":
11681217
return list(
11691218
self.__get_neighbors_indices_strictlyadjacent(param_config_index, param_config)
@@ -1348,6 +1397,8 @@ def get_neighbors_indices_no_cache(self, param_config: tuple, neighbor_method=No
13481397
self.__prepare_neighbors_index()
13491398

13501399
# if the passed param_config is fictious, we can not use the pre-calculated neighbors index
1400+
if neighbor_method == "Hamming-adjacent":
1401+
return self.__get_neighbors_indices_hammingadjacent(param_config_index, param_config)
13511402
if neighbor_method == "strictly-adjacent":
13521403
return self.__get_neighbors_indices_strictlyadjacent(param_config_index, param_config)
13531404
if neighbor_method == "adjacent":

kernel_tuner/strategies/hillclimbers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def base_hillclimb(base_sol: tuple, neighbor_method: str, max_fevals: int, searc
8282
child[index] = val
8383

8484
# get score for this position
85-
score = cost_func(child, check_restrictions=False)
85+
score = cost_func(child)
8686

8787
# generalize this to other tuning objectives
8888
if score < best_score:

test/test_searchspace.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,16 @@ def test_neighbors_hamming():
257257
assert random_neighbor != test_config
258258

259259

260+
def test_neighbors_hammingadjacent():
261+
"""Test whether the Hamming-adjacent neighbors are as expected."""
262+
test_config = tuple([1, 4, "string_1"])
263+
expected_neighbors = [
264+
(1.5, 4, 'string_1'),
265+
]
266+
267+
__test_neighbors(test_config, expected_neighbors, "Hamming-adjacent")
268+
269+
260270
def test_neighbors_strictlyadjacent():
261271
"""Test whether the strictly adjacent neighbors are as expected."""
262272
test_config = tuple([1, 4, "string_1"])
@@ -320,11 +330,19 @@ def test_neighbors_closest_param_indices():
320330
def test_neighbors_fictious():
321331
"""Test whether the neighbors are as expected for a fictious parameter configuration (i.e. not existing in the search space due to restrictions)."""
322332
test_config = tuple([1.5, 4, "string_1"])
333+
323334
expected_neighbors_hamming = [
324335
(1.5, 4, 'string_2'),
325336
(1.5, 5.5, 'string_1'),
326337
(3, 4, 'string_1'),
327338
]
339+
340+
expected_neighbors_hammingadjacent = [
341+
(1.5, 4, 'string_2'),
342+
(1.5, 5.5, 'string_1'),
343+
(3, 4, 'string_1'),
344+
]
345+
328346
expected_neighbors_strictlyadjacent = [
329347
(1.5, 5.5, 'string_2'),
330348
(1.5, 5.5, 'string_1'),
@@ -340,6 +358,7 @@ def test_neighbors_fictious():
340358
]
341359

342360
__test_neighbors_direct(test_config, expected_neighbors_hamming, "Hamming")
361+
__test_neighbors_direct(test_config, expected_neighbors_hammingadjacent, "Hamming-adjacent")
343362
__test_neighbors_direct(test_config, expected_neighbors_strictlyadjacent, "strictly-adjacent")
344363
__test_neighbors_direct(test_config, expected_neighbors_adjacent, "adjacent")
345364

0 commit comments

Comments
 (0)