156 lines
5.3 KiB
Python
156 lines
5.3 KiB
Python
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
|
#
|
|
# Licensed 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.
|
|
|
|
import csv
|
|
import os
|
|
|
|
from .utils import default_candidates, gbs_default_candidates
|
|
|
|
|
|
class AutoTuner:
|
|
"""
|
|
The AutoTuner can automatically provide running task based on user-defined settings
|
|
and the task will be launched for execution.
|
|
|
|
Args:
|
|
tuner_cfg (dict): The configuration of auto tuner user defined.
|
|
"""
|
|
|
|
def __init__(self, tuner_cfg):
|
|
self.cur_task_id = 1
|
|
self.task_limit = tuner_cfg.get("task_limit", 100)
|
|
|
|
search_algo = tuner_cfg.get("search_algo", {"name": "grid"})["name"]
|
|
|
|
if search_algo == "grid":
|
|
from .search import GridSearch
|
|
|
|
tuner_cfg["candidates"] = default_candidates(tuner_cfg)
|
|
self.algo = GridSearch(tuner_cfg)
|
|
elif search_algo == "dp_estimation":
|
|
from .search import DpEstimationSearch
|
|
|
|
tuner_cfg["candidates"] = default_candidates(tuner_cfg)
|
|
self.algo = DpEstimationSearch(tuner_cfg)
|
|
elif search_algo == "gbs":
|
|
from .search import GBSSearch
|
|
|
|
tuner_cfg["candidates"] = gbs_default_candidates(tuner_cfg)
|
|
self.algo = GBSSearch(tuner_cfg)
|
|
elif search_algo == "customize":
|
|
from .search import CustomizeSearch
|
|
|
|
self.algo = CustomizeSearch(tuner_cfg)
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
self.history_cfgs = []
|
|
self.resume_cfgs = []
|
|
self.tuner_cfg = tuner_cfg
|
|
|
|
def search_once(self):
|
|
"""Return a new task config."""
|
|
if self.cur_task_id > self.task_limit:
|
|
return None
|
|
new_cfg = self.algo.search_once(self.history_cfgs)
|
|
self.cur_task_id += 1
|
|
|
|
return new_cfg
|
|
|
|
def add_cfg(self, cfg):
|
|
"""Add cfg into history cfgs"""
|
|
self.history_cfgs.append(cfg)
|
|
|
|
def resume_form_history(self, history_csv_path="./history.csv"):
|
|
"""Resume form history csv file"""
|
|
# The breakpoint resume function does not start when the resume csv file does not exist.
|
|
if not os.path.exists(history_csv_path):
|
|
return
|
|
resume_csv_path = os.path.join(
|
|
os.path.dirname(history_csv_path),
|
|
f'{os.path.basename(history_csv_path).split(".")[0]}_copy.csv',
|
|
)
|
|
with open(history_csv_path, "r") as fread:
|
|
reader = csv.reader(fread)
|
|
data_list = list(reader)
|
|
with open(resume_csv_path, "w") as fwrite:
|
|
writer = csv.writer(fwrite)
|
|
for row in data_list:
|
|
writer.writerow(row)
|
|
# chang str type to real type
|
|
for row in data_list:
|
|
for i, value in enumerate(row):
|
|
try:
|
|
row[i] = int(value)
|
|
except ValueError:
|
|
try:
|
|
row[i] = float(value)
|
|
except ValueError:
|
|
pass
|
|
|
|
data_dict = []
|
|
keys = data_list[0]
|
|
values = data_list[1:]
|
|
for val in values:
|
|
val = [x if x != '' else None for x in val]
|
|
val = [True if x == 'True' else x for x in val]
|
|
val = [False if x == 'False' else x for x in val]
|
|
dictionary = dict(zip(keys, val))
|
|
time_val = -1
|
|
target_key = self.tuner_cfg["metric_cfg"]["name"]
|
|
if dictionary[target_key]:
|
|
time_val = dictionary[target_key]
|
|
dictionary["time"] = time_val
|
|
data_dict.append(dictionary)
|
|
self.resume_cfgs = data_dict
|
|
|
|
def get_cfg_from_resume(self, cur_cfg):
|
|
"""Get cfg from resume cfgs"""
|
|
keys_to_compare = [
|
|
'mp_degree',
|
|
'sharding_degree',
|
|
'pp_degree',
|
|
'dp_degree',
|
|
'sharding_stage',
|
|
'micro_batch_size',
|
|
'vpp_degree',
|
|
'use_recompute',
|
|
'recompute_granularity',
|
|
'num_gpus',
|
|
'nodes',
|
|
'global_batch_size',
|
|
'sharding_overlap',
|
|
'acc_steps',
|
|
]
|
|
|
|
if self.tuner_cfg.get("refined_recompute", None):
|
|
for rr in self.tuner_cfg["refined_recompute"]:
|
|
keys_to_compare.append(rr)
|
|
|
|
if self.tuner_cfg.get("custom_search_dim", None):
|
|
for key in self.tuner_cfg["custom_search_dim"]:
|
|
keys_to_compare.append(key)
|
|
|
|
for cfg in self.resume_cfgs:
|
|
ret_is_same = True
|
|
for key in keys_to_compare:
|
|
if not cfg.get(key) and not cur_cfg.get(key):
|
|
continue
|
|
else:
|
|
is_same = str(cfg.get(key)) == str(cur_cfg.get(key))
|
|
ret_is_same = ret_is_same and is_same
|
|
if ret_is_same:
|
|
return cfg
|
|
return None
|