# 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 copy import logging import os import subprocess logger = logging.getLogger('auto_tuner') _PRUNE_FUNC = [] _PRUNE_HISTORY_FUNC = [] def log_pruned_info(cur_cfg, pruned_reason, tuner_cfg): pruned_strategy = "DP{}_MP{}_PP{}_VPP{}_Sharding{}_Stage{}_MBS{}_Recompute_{}_Granularity_{}".format( cur_cfg["dp_degree"], cur_cfg["mp_degree"], cur_cfg["pp_degree"], cur_cfg["vpp_degree"], cur_cfg["sharding_degree"], cur_cfg["sharding_stage"], cur_cfg["micro_batch_size"], cur_cfg["use_recompute"], cur_cfg["recompute_granularity"], ) if "refined_recompute" in tuner_cfg: for key in tuner_cfg["refined_recompute"]: strategy = "".join(i.capitalize() for i in key.split("_")) strategy += str(cur_cfg[key]) pruned_strategy = pruned_strategy + "_" + strategy if "custom_search_dim" in tuner_cfg: for key in tuner_cfg["custom_search_dim"]: strategy = "".join(i.capitalize() for i in key.split("_")) strategy += str(cur_cfg[key]) pruned_strategy = pruned_strategy + "_" + strategy try: from paddle.distributed.launch.main import ctx ctx.logger.info( f"Strategy {pruned_strategy} has been pruned that {pruned_reason}" ) except: pass logger.info( f"Strategy {pruned_strategy} has been pruned that {pruned_reason}" ) def same_cfgs_beside(attrs, cur_cfg, history_cfgs=[]): """ Compare the current configuration with the history configuration, and obtain the same configurations as the current configuration except for the given attr. """ results = [] same = True for cfg in history_cfgs: for key in cur_cfg: if key in attrs: continue if key not in cfg or ( cfg[key] != cur_cfg[key] and key not in ["estimated_memory_usage"] ): same = False break if same: results.append(cfg) else: same = True return results def same_cfgs_beside_sharding_overlap(tuner_cfg, cur_cfg, history_cfgs=[]): result = None for cfg in history_cfgs: keys = [ "dp_degree", "mp_degree", "pp_degree", "vpp_degree", "micro_batch_size", "use_recompute", "recompute_granularity", "sharding_stage", ] same = True for key in keys: if cfg[key] != cur_cfg[key]: same = False break if same: result = cfg break return result def register_prune(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) _PRUNE_FUNC.append(wrapper) return wrapper def register_prune_history(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) _PRUNE_HISTORY_FUNC.append(wrapper) return wrapper @register_prune def prune_by_mp(tuner_cfg, cur_cfg, history_cfgs=[]): """ Prune by mp, the rules are: 1. MP degree should be evenly divided by hidden size and vocab size 2. MP degree should be in the candidates of user defined. 3. MP degree should be less than 8 if no candidates. """ mp_degree = cur_cfg.get("mp_degree", None) hidden_size = tuner_cfg["model_cfg"].get("hidden_size", None) vocab_size = tuner_cfg["model_cfg"].get("vocab_size", None) num_attention_heads = tuner_cfg["model_cfg"].get( "num_attention_heads", None ) seq_length = tuner_cfg["model_cfg"].get("seq_length", None) use_sequence_parallel = tuner_cfg.get("use_sequence_parallel", False) if mp_degree is None: return False if hidden_size and hidden_size % mp_degree != 0: return True if vocab_size and vocab_size % mp_degree != 0: return True if num_attention_heads and num_attention_heads % mp_degree != 0: return True if seq_length and seq_length % mp_degree != 0 and use_sequence_parallel: return True mp_degree_candidates = tuner_cfg.get("mp_degree", None) if mp_degree_candidates == "auto": mp_degree_candidates = tuner_cfg["candidates"]["mp_degree"] if mp_degree_candidates: if mp_degree not in mp_degree_candidates: return True return False @register_prune def prune_by_pp(tuner_cfg, cur_cfg, history_cfgs=[]): """ Prune by pp (pipeline-parallelism), the rules are: 1. PP degree should be evenly divided by number of layers. 2. PP degree should be in the candidates of user defined. 3. If no candidates, PP degree should be less than or equal to the number of nodes. """ pp_degree = cur_cfg.get("pp_degree", None) num_layers = tuner_cfg["model_cfg"].get("num_layers", None) num_nodes = ( cur_cfg["nodes"] if "nodes" in cur_cfg else tuner_cfg.get("nodes", 1) ) if pp_degree is None: return False if num_layers: if num_layers % pp_degree != 0: return True pp_degree_candidates = tuner_cfg.get("pp_degree", None) if pp_degree_candidates == "auto": pp_degree_candidates = tuner_cfg["candidates"]["pp_degree"] if pp_degree_candidates: if pp_degree not in pp_degree_candidates: return True else: if num_nodes != 1 and pp_degree > num_nodes: return True return False @register_prune_history def prune_by_mp_pp_history(tuner_cfg, cur_cfg, history_cfgs, pruned_cfgs): mp_degree = cur_cfg.get("mp_degree", None) pp_degree = cur_cfg.get("pp_degree", None) use_recompute = cur_cfg.get("recompute", None) if mp_degree is None or pp_degree is None or use_recompute is None: return False history_cfgs = copy.deepcopy(history_cfgs) history_cfgs.extend(pruned_cfgs) cfgs = same_cfgs_beside(["mp_degree", "pp_degree"], cur_cfg, history_cfgs) if cfgs: for cfg in cfgs: if ( not use_recompute and cfg["mp_degree"] * cfg["pp_degree"] == mp_degree * pp_degree and cfg["mp_degree"] > mp_degree and cfg.get("max_mem_usage") == "OOM" ): pruned_reason = f"mp_degree {mp_degree}, pp_degree {pp_degree} may cause oom because {cfg['mp_degree']}, {cfg['pp_degree']} already oom." log_pruned_info(cur_cfg, pruned_reason, tuner_cfg) cur_cfg["max_mem_usage"] = "OOM" return True return False @register_prune def prune_by_vpp(tuner_cfg, cur_cfg, history_cfgs=[]): """ Prune by vpp (virtual pipeline parallelism), the rules are: 1. VPP degree should be evenly divided by number of layers. 2. VPP degree should be in the candidates of user defined. """ pp_degree = cur_cfg.get("pp_degree", None) vpp_degree = cur_cfg.get("vpp_degree", None) num_layers = tuner_cfg["model_cfg"].get("num_layers", None) if pp_degree is None: return False if vpp_degree is None: return False if num_layers: global_batch_size = ( cur_cfg["global_batch_size"] if "global_batch_size" in cur_cfg else tuner_cfg["model_cfg"].get("global_batch_size", None) ) acc_steps = ( global_batch_size // cur_cfg["dp_degree"] // cur_cfg["sharding_degree"] // cur_cfg["micro_batch_size"] ) if vpp_degree > 1 and acc_steps % pp_degree != 0: return True if num_layers % (pp_degree * vpp_degree) != 0: return True if pp_degree == 1 and vpp_degree != 1: return True if pp_degree <= 2 and vpp_degree != 1: return True vpp_degree_candidates = tuner_cfg.get("vpp_degree", None) if vpp_degree_candidates == "auto": vpp_degree_candidates = tuner_cfg["candidates"]["vpp_degree"] if vpp_degree_candidates: if vpp_degree not in vpp_degree_candidates: return True return False @register_prune_history def prune_by_vpp_history(tuner_cfg, cur_cfg, history_cfgs=[], pruned_cfgs=[]): vpp_degree = cur_cfg.get("vpp_degree", None) if vpp_degree is None: return False history_cfgs = copy.deepcopy(history_cfgs) history_cfgs.extend(pruned_cfgs) cfgs = same_cfgs_beside("vpp_degree", cur_cfg, history_cfgs) if cfgs: for cfg in cfgs: # memory prune if ( cfg["vpp_degree"] > vpp_degree and cfg.get("max_mem_usage") == "OOM" ): pruned_reason = f"vpp_degree {vpp_degree} may cause oom because {cfg['vpp_degree']} already oom." log_pruned_info(cur_cfg, pruned_reason, tuner_cfg) cur_cfg["max_mem_usage"] = "OOM" return True return False @register_prune def prune_by_mbs(tuner_cfg, cur_cfg, history_cfgs=[]): """ Prune by mbs (micro batch size), the rules are: 1. Micro batch size should be evenly divided by the local batch size. 2. Micro batch size should be in the candidates of user defined. 3. Prune if a similar configuration with a larger micro batch size resulted in a valid run. """ micro_batch_size = cur_cfg.get("micro_batch_size", None) global_batch_size = ( cur_cfg["global_batch_size"] if "global_batch_size" in cur_cfg else tuner_cfg["model_cfg"].get("global_batch_size", None) ) if global_batch_size == "auto": global_batch_size = cur_cfg["global_batch_size"] if global_batch_size: local_batch_size = ( global_batch_size // cur_cfg["dp_degree"] // cur_cfg["sharding_degree"] ) if local_batch_size == 0: return True mbs_candidates = tuner_cfg.get("micro_batch_size", None) if mbs_candidates == "auto": mbs_candidates = tuner_cfg["candidates"]["micro_batch_size"] if micro_batch_size is None: return False if local_batch_size: if local_batch_size % micro_batch_size != 0: return True acc_steps = local_batch_size // micro_batch_size pp_degree = cur_cfg.get("pp_degree", None) if pp_degree is not None: if acc_steps < pp_degree: return True vpp_degree = cur_cfg.get("vpp_degree", None) if vpp_degree is not None and vpp_degree > 1: if pp_degree is not None: if acc_steps % pp_degree != 0: return True if mbs_candidates: if micro_batch_size not in mbs_candidates: return True return False @register_prune_history def prune_by_mbs_history(tuner_cfg, cur_cfg, history_cfgs=[], pruned_cfgs=[]): micro_batch_size = cur_cfg.get("micro_batch_size", None) if micro_batch_size is None: return False history_cfgs = copy.deepcopy(history_cfgs) history_cfgs.extend(pruned_cfgs) cfgs = same_cfgs_beside( ["micro_batch_size", "acc_steps"], cur_cfg, history_cfgs ) if cfgs: for cfg in cfgs: if ( cfg["micro_batch_size"] > micro_batch_size and cfg.get("time", -1) > 0 ): pruned_reason = f"micro_batch_size {micro_batch_size} may be slower because {cfg['micro_batch_size']} has been already runnable." log_pruned_info(cur_cfg, pruned_reason, tuner_cfg) cur_cfg["time"] = cfg["time"] return True # memory prune if ( cfg["micro_batch_size"] < micro_batch_size and cfg.get("max_mem_usage") == "OOM" ): pruned_reason = f"micro_batch_size {micro_batch_size} may cause oom because {cfg['micro_batch_size']} already oom." log_pruned_info(cur_cfg, pruned_reason, tuner_cfg) cur_cfg["max_mem_usage"] = "OOM" return True return False @register_prune def prune_by_sharding(tuner_cfg, cur_cfg, history_cfgs=[]): """ Prune by sharding parameters, the rules are: 1. Sharding stage and sharding degree should be specified. 2. Sharding stage and degree should be in the candidates of user defined. 3. If PP (pipeline-parallelism) degree is not 1, sharding stage must be 1. 4. Prune if a similar configuration with a lower sharding stage resulted in a valid run. 5. If sharding degree is 1, sharding stage is invalid. """ sharding_stage = cur_cfg.get("sharding_stage", None) sharding_degree = cur_cfg.get("sharding_degree", None) pp_degree = cur_cfg.get("pp_degree", None) if not sharding_stage: return False if not sharding_degree: return False sharding_stage_candidates = tuner_cfg.get("sharding_stage", None) if sharding_stage_candidates == "auto": sharding_stage_candidates = tuner_cfg["candidates"]["sharding_stage"] sharding_degree_candidates = tuner_cfg.get("sharding_degree", None) if sharding_degree_candidates == "auto": sharding_degree_candidates = tuner_cfg["candidates"]["sharding_degree"] if sharding_stage_candidates: if sharding_stage not in sharding_stage_candidates: return True if sharding_degree_candidates: if sharding_degree not in sharding_degree_candidates: return True if ( pp_degree and pp_degree != 1 and sharding_stage != 1 and sharding_degree != 1 ): return True if sharding_degree == 1: cfgs = same_cfgs_beside("sharding_stage", cur_cfg, history_cfgs) if cfgs: return True return False @register_prune_history def prune_by_sharding_history( tuner_cfg, cur_cfg, history_cfgs=[], pruned_cfgs=[] ): sharding_degree = cur_cfg.get("sharding_degree", None) if sharding_degree is None: return False sharding_stage = cur_cfg.get("sharding_stage", None) if sharding_stage is None: return False history_cfgs = copy.deepcopy(history_cfgs) history_cfgs.extend(pruned_cfgs) cfgs = same_cfgs_beside("sharding_stage", cur_cfg, history_cfgs) if cfgs: for cfg in cfgs: if ( cfg["sharding_stage"] < sharding_stage and cfg.get("time", -1) > 0 ): pruned_reason = f"sharding_stage {sharding_stage} may be slower because {cfg['sharding_stage']} has been already runnable." log_pruned_info(cur_cfg, pruned_reason, tuner_cfg) cur_cfg["time"] = cfg["time"] return True # memory prune if ( cfg["sharding_stage"] > sharding_stage and cfg.get("max_mem_usage") == "OOM" ): pruned_reason = f"sharding_stage {sharding_stage} may cause oom because {cfg['sharding_stage']} already oom." log_pruned_info(cur_cfg, pruned_reason, tuner_cfg) cur_cfg["max_mem_usage"] = "OOM" return True return False @register_prune def prune_by_recompute(tuner_cfg, cur_cfg, history_cfgs=[]): """ Prune by recompute parameters, the rules are: 1. If recompute is not used, return False directly. 2. Usage of recompute and recompute granularity should be in the candidates of user defined. 3. If recompute is not used, but recompute granularity is set, return True for pruning. 4. Prune if a similar configuration without using recompute resulted in a valid run. 5. If recompute is false, prune redundant recompute granularity """ recompute_granularity = cur_cfg.get("recompute_granularity", None) use_recompute = cur_cfg.get("use_recompute", None) recompute_level = get_config_recompute_level(cur_cfg) if use_recompute is None: return False recompute_granularity_candidates = tuner_cfg["candidates"].get( "recompute_granularity", None ) use_recompute_candidates = tuner_cfg["candidates"].get( "use_recompute", None ) if use_recompute_candidates: if use_recompute not in use_recompute_candidates: return True if recompute_granularity_candidates and recompute_granularity: if recompute_granularity not in recompute_granularity_candidates: return True if not use_recompute: if recompute_granularity != "full": return True cfgs = same_cfgs_beside( ["use_recompute", "recompute_granularity"], cur_cfg, history_cfgs ) if cfgs: for cfg in cfgs: if recompute_level == get_config_recompute_level(cfg): return True return False def get_config_recompute_level(cfg): recompute_granularity_level = {"full": 3, "full_attn": 2, "core_attn": 1} use_recompute = cfg.get("use_recompute", None) recompute_granularity = cfg.get("recompute_granularity", None) if use_recompute is None: return None if not use_recompute: return 0 else: return recompute_granularity_level[recompute_granularity] @register_prune_history def prune_by_recompute_history( tuner_cfg, cur_cfg, history_cfgs=[], pruned_cfgs=[] ): recompute_level = get_config_recompute_level(cur_cfg) if recompute_level is None: return False history_cfgs = copy.deepcopy(history_cfgs) history_cfgs.extend(pruned_cfgs) cfgs = same_cfgs_beside( ["use_recompute", "recompute_granularity"], cur_cfg, history_cfgs ) if cfgs: for cfg in cfgs: cfg["recompute_level"] = get_config_recompute_level(cfg) if ( cfg["recompute_level"] < recompute_level and cfg.get("time", -1) > 0 ): pruned_reason = f"use_recompute may be slower because {cfg['use_recompute']} has been already runnable." log_pruned_info(cur_cfg, pruned_reason, tuner_cfg) cur_cfg["time"] = cfg["time"] return True if ( cfg["recompute_level"] > recompute_level and cfg.get("max_mem_usage") == "OOM" ): pruned_reason = f"use_recompute may cause oom because {cfg['use_recompute']} already oom." log_pruned_info(cur_cfg, pruned_reason, tuner_cfg) cur_cfg["max_mem_usage"] = "OOM" return True return False @register_prune def prune_by_num_gpus(tuner_cfg, cur_cfg, history_cfgs=[]): num_gpus = ( cur_cfg["num_gpus"] if "num_gpus" in cur_cfg else tuner_cfg.get("num_gpus") ) dp_degree = cur_cfg.get("dp_degree", 1) mp_degree = cur_cfg.get("mp_degree", 1) pp_degree = cur_cfg.get("pp_degree", 1) sharding_degree = cur_cfg.get("sharding_degree", 1) if dp_degree * mp_degree * pp_degree * sharding_degree != num_gpus: return True return False @register_prune def prune_by_memory_estimation(tuner_cfg, cur_cfg, history_cfgs=[]): memory_estimation_tool = tuner_cfg.get("memory_estimation_tool", None) # TODO(@gexiao): get from system api max_memory_usage = tuner_cfg.get("max_mem_usage", None) model_cfg = tuner_cfg["model_cfg"] if memory_estimation_tool is None: return False if not os.path.exists(memory_estimation_tool): raise ValueError( f"memory_estimation_tool should be a valid path, but got {memory_estimation_tool}" ) if max_memory_usage is None: raise ValueError( "max_mem_usage should be set when using memory estimation tool" ) # get distributed strategy dp_degree = cur_cfg['dp_degree'] mp_degree = cur_cfg['mp_degree'] pp_degree = cur_cfg['pp_degree'] vpp_degree = cur_cfg['vpp_degree'] sharding_degree = cur_cfg['sharding_degree'] sharding_stage = cur_cfg['sharding_stage'] use_recompute = cur_cfg['use_recompute'] micro_batch_size = cur_cfg['micro_batch_size'] recompute_granularity = cur_cfg['recompute_granularity'] memory_estimation_cmd = [ "python", memory_estimation_tool, "--dp_degree", str(dp_degree), "--mp_degree", str(mp_degree), "--pp_degree", str(pp_degree), "--vpp_degree", str(vpp_degree), "--sharding_degree", str(sharding_degree), "--sharding_stage", str(sharding_stage), "--use_recompute", str(use_recompute), "--micro_batch_size", str(micro_batch_size), "--recompute_granularity", str(recompute_granularity), ] # get model config hidden_size = model_cfg.get('hidden_size', None) if hidden_size is not None: memory_estimation_cmd.extend(["--hidden_size", str(hidden_size)]) num_attention_heads = model_cfg.get('num_attention_heads', None) if num_attention_heads is not None: memory_estimation_cmd.extend( ["--num_attention_heads", str(num_attention_heads)] ) num_layers = model_cfg.get('num_layers', None) if num_layers is not None: memory_estimation_cmd.extend(["--num_layers", str(num_layers)]) max_sequence_length = model_cfg.get('max_sequence_length', None) if max_sequence_length is not None: memory_estimation_cmd.extend( ["--max_sequence_length", str(max_sequence_length)] ) vocab_size = model_cfg.get('vocab_size', None) if vocab_size is not None: memory_estimation_cmd.extend(["--vocab_size", str(vocab_size)]) intermediate_size = model_cfg.get('intermediate_size', None) if intermediate_size is not None: memory_estimation_cmd.extend( ["--intermediate_size", str(intermediate_size)] ) result = subprocess.run( memory_estimation_cmd, capture_output=True, text=True, ) if result.returncode == 0: cur_memory_usage = int(round(float(result.stdout), 2)) cur_cfg["estimated_memory_usage"] = cur_memory_usage msg = f"Estimated {cur_cfg} memory usage: {cur_memory_usage} MB" memory_exceeded = cur_memory_usage > (max_memory_usage * 1024) if memory_exceeded: msg += ", it will be pruned!" logger.info(msg) return memory_exceeded else: raise ValueError( f"memory_estimation_tool failed with error: {result.stderr}" ) @register_prune_history def prune_by_sharding_overlap( tuner_cfg, cur_cfg, history_cfgs=[], pruned_cfgs=[] ): """Prune by sharding overlap for single dp estimation""" if "sharding_overlap" in cur_cfg: result = same_cfgs_beside_sharding_overlap( tuner_cfg, cur_cfg, history_cfgs ) if not result: return True if not result[tuner_cfg['metric_cfg']['name']]: return True return False def is_invalid(cur_cfg, invalid_strategy): mapping = { "dp_degree": "dp", "mp_degree": "mp", "pp_degree": "pp", "vpp_degree": "vpp", "micro_batch_size": "mbs", "sharding_degree": "sharding", "sharding_stage": "stage", "use_recompute": "recompute", "recompute_granularity": "granularity", } granularity_mapping = {0: "full", 1: "full_attn", 2: "core_attn"} reversed_mapping = {} for key in mapping: reversed_mapping[mapping[key]] = key for strategy in invalid_strategy: assert isinstance(strategy, str) dims = strategy.split("_") has_matched = 0 for dim in dims: matched = None for key in reversed_mapping: if dim.startswith(key): matched = key break if matched: value = dim[len(matched)] # * means this strategy turned on if matched in ["dp", "mp", "pp", "vpp", "sharding"]: if value == "*": if cur_cfg[reversed_mapping[matched]] != 1: has_matched += 1 continue else: value = int(value) if cur_cfg[reversed_mapping[matched]] == value: has_matched += 1 continue elif matched == "recompute": if value == "*": if cur_cfg[reversed_mapping[matched]]: has_matched += 1 continue else: value = bool(int(value)) if cur_cfg[reversed_mapping[matched]] == value: has_matched += 1 continue elif matched == "stage": if value == "*": if cur_cfg[reversed_mapping["sharding"]] != 1: has_matched += 1 continue else: value = int(value) if cur_cfg[reversed_mapping[matched]] == value: has_matched += 1 continue elif matched == "mbs": if value == "*": has_matched += 1 continue else: value = int(value) if cur_cfg[reversed_mapping[matched]] == value: has_matched += 1 continue elif matched == "granularity": if value == "*": if cur_cfg[reversed_mapping["use_recompute"]]: has_matched += 1 continue else: value = int(value) granularity = granularity_mapping[value] if cur_cfg[reversed_mapping[matched]] == granularity: has_matched += 1 continue if has_matched == len(dims): return True return False @register_prune def prune_by_invalid_strategy(tuner_cfg, cur_cfg, history_cfgs=[]): if tuner_cfg.get("invalid_strategy", None): invalid_strategy = tuner_cfg["invalid_strategy"] assert isinstance(invalid_strategy, list) if is_invalid(cur_cfg, invalid_strategy): return True return False @register_prune def prune_by_refined_recompute(tuner_cfg, cur_cfg, history_cfgs=[]): if tuner_cfg.get("refined_recompute", None): rr = tuner_cfg.get("refined_recompute") pp_degree = cur_cfg["pp_degree"] recompute = cur_cfg["use_recompute"] recompute_granularity = cur_cfg["recompute_granularity"] compare = [cur_cfg[item] for item in rr] if recompute: if recompute_granularity and recompute_granularity != "full": if compare.count(0) != len(compare): return True if pp_degree == 1 and compare.count(0) != len(compare): return True if tuner_cfg["model_cfg"]["num_layers"] % pp_degree != 0: return True max_value = tuner_cfg["model_cfg"]["num_layers"] / pp_degree if cur_cfg[rr[0]] > max_value: return True i = 1 while i < len(rr): if cur_cfg[rr[i]] > max_value or ( cur_cfg[rr[i - 1]] != max_value and cur_cfg[rr[i]] != 0 ): return True i += 1 return False @register_prune_history def prune_by_refined_recompute_history( tuner_cfg, cur_cfg, history_cfgs=[], pruned_cfgs=[] ): if tuner_cfg.get("refined_recompute", None): history_cfgs = copy.deepcopy(history_cfgs) history_cfgs.extend(pruned_cfgs) rr = tuner_cfg.get("refined_recompute") compare = copy.deepcopy(rr) compare.append("use_recompute") cfgs = same_cfgs_beside(compare, cur_cfg, history_cfgs) for item in rr: if cfgs: for cfg in cfgs: if not cfg["use_recompute"] and cfg.get("time", -1) > 0: pruned_reason = f"{item} {cur_cfg[item]} may be slower because not recompute has been already runnable." log_pruned_info(cur_cfg, pruned_reason, tuner_cfg) cur_cfg["time"] = cfg["time"] return True if ( cfg[item] > cur_cfg[item] and cfg.get("time", -1) > 0 and cfg["use_recompute"] and cur_cfg["use_recompute"] ): pruned_reason = f"{item} {cur_cfg[item]} may be slower because {cfg[item]} has been already runnable." log_pruned_info(cur_cfg, pruned_reason, tuner_cfg) cur_cfg["time"] = cfg["time"] return True # memory prune if ( cfg[item] < cur_cfg[item] and cfg.get("max_mem_usage") == "OOM" and cfg["use_recompute"] and cur_cfg["use_recompute"] ): pruned_reason = f"{item} {cur_cfg[item]} may cause oom because {cfg[item]} already oom." log_pruned_info(cur_cfg, pruned_reason, tuner_cfg) cur_cfg["max_mem_usage"] = "OOM" return True return False @register_prune_history def prune_by_custom_search_dim_history( tuner_cfg, cur_cfg, history_cfgs=[], pruned_cfgs=[] ): history_cfgs = copy.deepcopy(history_cfgs) custom_search_dim = tuner_cfg.get("custom_search_dim", None) prune_custom_search_dim = [] custom_dim_level = {} if custom_search_dim is not None: for key, value in custom_search_dim.items(): if value["prune"]: prune_custom_search_dim.append(key) # In the custom_search_dim, the values are ordered according to the sequence specified in its custom configuration. custom_dim_level[key] = { key: value for value, key in enumerate(value["value"]) } for key in prune_custom_search_dim: history_cfgs.extend(pruned_cfgs) cfgs = same_cfgs_beside(key, cur_cfg, history_cfgs) cur_value = cur_cfg.get(key, None) if cur_value is None: return False # In the custom_search_dim, based on the order of values provided in its custom configuration, if a configuration is found to be executable, the subsequent configurations will be pruned. if cfgs: for cfg in cfgs: cfg_value = cfg[key] if ( custom_dim_level[key][cfg_value] < custom_dim_level[key][cur_value] and cfg.get("time", -1) > 0 ): pruned_reason = f"{key}{cfg_value} may be slower because {key}{cur_value} has been already runnable." log_pruned_info(cur_cfg, pruned_reason, tuner_cfg) cur_cfg["time"] = cfg["time"] return True return False