# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. """Droplet algorithm""" import os import numpy as np # type: ignore from .space import Space from .utils import get_time, write_file class Droplet: """Tuner with droplet algorithm in Meta Schedule. Parameters ---------- json_file: str json format file target: hardware target log: str path to save json file trials: int number of samples, the default is 100 pvalue: float statistical value to confidence level, the default is 0.05 """ def __init__(self, json_file, workload_file, target, log, pvalue=0.05) -> None: self.space = Space(json_file, workload_file, target) self.final_log = write_file([json_file], log) self.pvalue = pvalue self.next = [(0, [0] * len(self.space.dims))] best_avg, _ = get_time(log) self.best_choice = [0, [0] * len(self.space.dims), best_avg] self.count, self.execution, self.found_best_pos = 1, 1, True self.total_execution = 1 if len(self.space.dims) > 0: self.total_execution = max(self.space.dims) self.dims, self.step = self.space.dims, 1 self.visited, self.batch = set([0]), max(os.cpu_count(), len(self.dims)) def next_batch(self, batch_size): i, json_file_list = 0, [] while i < len(self.next): if batch_size > 0 and self.count >= self.trials: break json_file_list.append(self.space.template(values=self.next[i][1], create=False)) i, self.count = i + 1, self.count + 1 return self.space.run(json_file_list, self.final_log) def has_next(self): return len(self.next) > 0 and self.found_best_pos def tune(self, n_trial=100): self.trials = n_trial self.speculation() while self.has_next(): res = self.next_batch(self.batch) self.update(res) def num_to_bin(self, value, factor=1): bin_format = str(0) * (len(self.dims) - len(bin(value)[2:])) + bin(value)[2:] return [int(i) * factor for i in bin_format] def search_space(self, factor=1): "create a search space" search_space: list = [] for i in range(0, len(self.space.dims)): if len(search_space) > self.batch - len(self.next): break space = self.num_to_bin(2**i, factor) idx = self.space.knob2point(space) if idx not in self.visited: search_space.append(space) return search_space def next_pos(self, new_positions): "returns the neighbors of the best solution" next_set = [] for p in new_positions: new_p = [ (x + y) % self.dims[i] if (x + y > 0) else 0 for i, (x, y) in enumerate(zip(p, self.best_choice[1])) ] idx_p = self.space.knob2point(new_p) if idx_p not in self.visited: self.visited.add(idx_p) next_set.append((idx_p, new_p)) return next_set def speculation(self): # Gradient descending direction prediction and search space filling while len(self.next) < self.batch and self.execution < self.total_execution: self.next += self.next_pos(self.search_space(self.execution)) self.execution += self.step def update(self, results): """Update the values""" self.found_best_pos, count_valids = False, 0 for i, res in enumerate(results): if np.mean(self.best_choice[2]) > np.mean(res): self.best_choice = [self.next[i][0], self.next[i][1], res] self.found_best_pos = True if np.mean(res) != 10000: count_valids += 1 self.next = [] # stop, because all neighborhoods are invalid. if count_valids == 0: self.speculation() self.found_best_pos = True return if self.found_best_pos: self.next += self.next_pos(self.search_space()) self.execution = 1 self.speculation()