ZhengyangZhang commited on
Commit
b6062dd
·
verified ·
1 Parent(s): 16a2d8c

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/deepspeed/__pycache__/__init__.cpython-312.pyc +0 -0
  2. lib/python3.12/site-packages/deepspeed/__pycache__/constants.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/deepspeed/__pycache__/env_report.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/deepspeed/__pycache__/git_version_info.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/deepspeed/__pycache__/git_version_info_installed.cpython-312.pyc +0 -0
  6. lib/python3.12/site-packages/deepspeed/autotuning/__init__.py +6 -0
  7. lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/__init__.cpython-312.pyc +0 -0
  8. lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/autotuner.cpython-312.pyc +0 -0
  9. lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/config.cpython-312.pyc +0 -0
  10. lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/constants.cpython-312.pyc +0 -0
  11. lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/scheduler.cpython-312.pyc +0 -0
  12. lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/utils.cpython-312.pyc +0 -0
  13. lib/python3.12/site-packages/deepspeed/autotuning/autotuner.py +1113 -0
  14. lib/python3.12/site-packages/deepspeed/autotuning/config.py +98 -0
  15. lib/python3.12/site-packages/deepspeed/autotuning/config_templates/template_zero0.json +5 -0
  16. lib/python3.12/site-packages/deepspeed/autotuning/config_templates/template_zero1.json +7 -0
  17. lib/python3.12/site-packages/deepspeed/autotuning/config_templates/template_zero2.json +11 -0
  18. lib/python3.12/site-packages/deepspeed/autotuning/config_templates/template_zero3.json +17 -0
  19. lib/python3.12/site-packages/deepspeed/autotuning/constants.py +185 -0
  20. lib/python3.12/site-packages/deepspeed/autotuning/scheduler.py +432 -0
  21. lib/python3.12/site-packages/deepspeed/autotuning/tuner/__init__.py +8 -0
  22. lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/__init__.cpython-312.pyc +0 -0
  23. lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/base_tuner.cpython-312.pyc +0 -0
  24. lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/cost_model.cpython-312.pyc +0 -0
  25. lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/index_based_tuner.cpython-312.pyc +0 -0
  26. lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/model_based_tuner.cpython-312.pyc +0 -0
  27. lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/utils.cpython-312.pyc +0 -0
  28. lib/python3.12/site-packages/deepspeed/autotuning/tuner/base_tuner.py +72 -0
  29. lib/python3.12/site-packages/deepspeed/autotuning/tuner/cost_model.py +66 -0
  30. lib/python3.12/site-packages/deepspeed/autotuning/tuner/index_based_tuner.py +40 -0
  31. lib/python3.12/site-packages/deepspeed/autotuning/tuner/model_based_tuner.py +157 -0
  32. lib/python3.12/site-packages/deepspeed/autotuning/tuner/utils.py +86 -0
  33. lib/python3.12/site-packages/deepspeed/autotuning/utils.py +459 -0
  34. lib/python3.12/site-packages/deepspeed/checkpoint/__init__.py +20 -0
  35. lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/__init__.cpython-312.pyc +0 -0
  36. lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/constants.cpython-312.pyc +0 -0
  37. lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/deepspeed_checkpoint.cpython-312.pyc +0 -0
  38. lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/ds_to_universal.cpython-312.pyc +0 -0
  39. lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/reshape_3d_utils.cpython-312.pyc +0 -0
  40. lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/reshape_meg_2d.cpython-312.pyc +0 -0
  41. lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/reshape_utils.cpython-312.pyc +0 -0
  42. lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/universal_checkpoint.cpython-312.pyc +0 -0
  43. lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/utils.cpython-312.pyc +0 -0
  44. lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/zero_checkpoint.cpython-312.pyc +0 -0
  45. lib/python3.12/site-packages/deepspeed/checkpoint/constants.py +87 -0
  46. lib/python3.12/site-packages/deepspeed/checkpoint/deepspeed_checkpoint.py +307 -0
  47. lib/python3.12/site-packages/deepspeed/checkpoint/ds_to_universal.py +549 -0
  48. lib/python3.12/site-packages/deepspeed/checkpoint/reshape_3d_utils.py +111 -0
  49. lib/python3.12/site-packages/deepspeed/checkpoint/reshape_meg_2d.py +222 -0
  50. lib/python3.12/site-packages/deepspeed/checkpoint/reshape_utils.py +113 -0
lib/python3.12/site-packages/deepspeed/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (15.7 kB). View file
 
lib/python3.12/site-packages/deepspeed/__pycache__/constants.cpython-312.pyc ADDED
Binary file (572 Bytes). View file
 
lib/python3.12/site-packages/deepspeed/__pycache__/env_report.cpython-312.pyc ADDED
Binary file (11.6 kB). View file
 
lib/python3.12/site-packages/deepspeed/__pycache__/git_version_info.cpython-312.pyc ADDED
Binary file (1.3 kB). View file
 
lib/python3.12/site-packages/deepspeed/__pycache__/git_version_info_installed.cpython-312.pyc ADDED
Binary file (592 Bytes). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ from .autotuner import Autotuner
lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (247 Bytes). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/autotuner.cpython-312.pyc ADDED
Binary file (56.8 kB). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/config.cpython-312.pyc ADDED
Binary file (5.37 kB). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/constants.cpython-312.pyc ADDED
Binary file (5.68 kB). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/scheduler.cpython-312.pyc ADDED
Binary file (22.5 kB). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/utils.cpython-312.pyc ADDED
Binary file (20.4 kB). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/autotuner.py ADDED
@@ -0,0 +1,1113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ import shutil
7
+ import subprocess
8
+ import time
9
+ import datetime
10
+ import math
11
+ import hjson
12
+
13
+ from ..runtime.config_utils import dict_raise_error_on_duplicate_keys
14
+ from ..runtime.constants import *
15
+
16
+ from ..runtime.zero.config import ZERO_OPTIMIZATION, ZeroStageEnum
17
+ from ..utils import logger
18
+ from .config import DeepSpeedAutotuningConfig
19
+ from .constants import *
20
+ from .scheduler import ResourceManager
21
+ from .tuner import GridSearchTuner, RandomTuner, ModelBasedTuner
22
+ from .utils import *
23
+ from deepspeed.accelerator import get_accelerator
24
+
25
+ try:
26
+ from tabulate import tabulate
27
+ except ImportError:
28
+ tabulate = None
29
+
30
+ try:
31
+ import mlflow
32
+ has_mlflow = True
33
+ except Exception as e:
34
+ has_mlflow = False
35
+
36
+ ZERO_OPTIMIZATION_STAGE = "stage"
37
+ OFFLOAD_OPTIMIZER = "offload_optimizer"
38
+ OFFLOAD_PARAM = "offload_param"
39
+ ZERO_OPTIMIZATION_STAGE_DEFAULT = ZeroStageEnum.disabled
40
+
41
+
42
+ class Autotuner:
43
+ """The DeepSpeed Autotuner automatically discovers the optimal DeepSpeed configuration that delivers good training speed. The Autotuner uses model information, system information, and heuristics to efficiently tune system knobs that affect compute and memory efficiencies, such as ZeRO optimization stages, micro-batch sizes, and many other ZeRO optimization configurations. It not only reduces the time and resources user spend on tuning, but also can discover configurations better than hand-tuned methods.
44
+ Autotuning with DeepSpeed requires no code change from DeepSpeed users. Please refer to the README for usage details.
45
+ """
46
+
47
+ def __init__(self, args, active_resources):
48
+ self.args = args
49
+ self.selected_exp_dir = None
50
+
51
+ assert tabulate is not None, "Missing required package `tabulate`, please install with `pip install deepspeed[autotuning]`."
52
+
53
+ logger.debug(f"autotuning args={args}")
54
+
55
+ self.user_config = self._get_user_config(args.user_args)
56
+ assert self.user_config is not None, "DeepSpeed configuration is not provided"
57
+
58
+ self.autotuning_config = DeepSpeedAutotuningConfig(self.user_config)
59
+ if self.user_config[AUTOTUNING]:
60
+ if AUTOTUNING_EXPS_DIR in self.user_config[AUTOTUNING].keys():
61
+ del self.user_config[AUTOTUNING][AUTOTUNING_EXPS_DIR]
62
+ if AUTOTUNING_RESULTS_DIR in self.user_config[AUTOTUNING].keys():
63
+ del self.user_config[AUTOTUNING][AUTOTUNING_RESULTS_DIR]
64
+
65
+ self.exps_dir = self.autotuning_config.exps_dir
66
+ if self.autotuning_config.overwrite and os.path.exists(self.exps_dir):
67
+ shutil.rmtree(self.exps_dir, ignore_errors=True)
68
+ if not os.path.exists(self.exps_dir):
69
+ try:
70
+ os.makedirs(self.exps_dir, exist_ok=True)
71
+ logger.info(f"Created autotuning experiments directory: {self.exps_dir}")
72
+ except:
73
+ logger.error(
74
+ f"Failed to create {self.exps_dir}, please check exps_dir in the autotuning config file is accessible by all the nodes in the job."
75
+ )
76
+ exit(-1)
77
+
78
+ self.results_dir = self.autotuning_config.results_dir
79
+ if self.autotuning_config.overwrite and os.path.exists(self.results_dir):
80
+ shutil.rmtree(self.results_dir, ignore_errors=True)
81
+ if not os.path.exists(self.results_dir):
82
+ try:
83
+ os.makedirs(self.results_dir, exist_ok=True)
84
+ logger.info(f"Created autotuning results directory: {self.exps_dir}")
85
+ except:
86
+ logger.error(
87
+ f"Failed to create {self.results_dir}, please check results_dir in the autotuning config file is accessible by all the nodes in the job."
88
+ )
89
+ exit(-1)
90
+
91
+ # set the active resource for the autotuner resource manager
92
+ self.rm = self._get_resource_manager(active_resources)
93
+
94
+ # get resource requirement for each autotuning experiment
95
+ self.exp_num_nodes, self.exp_num_gpus = self._get_exp_resources(args)
96
+
97
+ assert self.exp_num_gpus <= self.rm.num_gpus_per_node, "num_gpus in the autotuning configuration must not be less than the --num_gpus value in the train script if any"
98
+ assert self.exp_num_nodes <= len(
99
+ self.rm.nodes
100
+ ), "num_nodes in the autotuning configuration must not be less than the --num_nodes value in the train script if any"
101
+
102
+ self.records = {}
103
+ self.optimal_cmd = None
104
+ self.optimal_ds_config = None
105
+
106
+ self.mlflow_parent_id = None
107
+
108
+ def print_tuning_results(self):
109
+ """Print the autotuning results in tabular format.
110
+ """
111
+ best_space_records = self.get_best_space_records()
112
+ tab = []
113
+ if best_space_records:
114
+ for key, val in best_space_records.items():
115
+ if not val:
116
+ continue
117
+ row = []
118
+ row.append(key)
119
+ num_exps = 0
120
+ if key == GLOBAL_TUNING_SPACE:
121
+ cnt = 0
122
+ for k, v in best_space_records.items():
123
+ if k != GLOBAL_TUNING_SPACE:
124
+ cnt += v[2]
125
+ num_exps = cnt
126
+ else:
127
+ num_exps = val[2]
128
+ row.append(num_exps)
129
+ row.append(val[1])
130
+ row.append(val[0]['name'])
131
+ tab.append(row)
132
+ summary = tabulate(tab,
133
+ headers=["tuning_space", "num_experiments", "best_metric_val", "best_exp_name"],
134
+ tablefmt="pipe")
135
+ print(summary)
136
+ with open(os.path.join(self.results_dir, 'summary.txt'), 'w', buffering=BUFSIZE) as fd:
137
+ fd.write(summary)
138
+ fd.flush()
139
+ os.fsync(fd)
140
+
141
+ if GLOBAL_TUNING_SPACE in best_space_records:
142
+ best_exp, best_metric_val, total_num_exps = best_space_records[GLOBAL_TUNING_SPACE]
143
+ if best_exp:
144
+ logger.info(
145
+ f"{best_exp['name']} is the optimal setup after tuning. The exp result is at {best_exp['result_dir']}."
146
+ )
147
+ else:
148
+ logger.info(f"No optimal setup is found. Please check that experiments were run successfully.")
149
+ tuning_duration = datetime.timedelta(seconds=(time.time() - self.start_time))
150
+
151
+ logger.info(f"Tuning completed in {tuning_duration}")
152
+ with open(os.path.join(self.results_dir, 'summary.txt'), 'a') as f:
153
+ f.write(
154
+ f"\n\nTuning completed in {tuning_duration}. Total number of experiments: {self.rm.experiment_count - 1}."
155
+ )
156
+ f.flush()
157
+
158
+ def _get_user_config(self, user_args):
159
+ """Get DeepSpeed configuration from the user arguments passed to the launcher.
160
+
161
+ Args:
162
+ user_args ([list]): user arguments passed to the DeepSpeed launcher
163
+
164
+ Returns:
165
+ [dict]: DeepSpeed configuration dictionary
166
+ """
167
+ user_config_file = None
168
+ if "--deepspeed_config" in user_args:
169
+ idx = user_args.index("--deepspeed_config")
170
+ assert ".json" in user_args[
171
+ idx + 1], "DeepSpeed --deepspeed_config requires a json file to specify the configuration"
172
+
173
+ user_config_file = user_args[idx + 1]
174
+ elif "--deepspeed" in user_args:
175
+ idx = user_args.index("--deepspeed")
176
+ if ".json" in user_args[idx + 1]:
177
+ user_config_file = user_args[idx + 1]
178
+
179
+ logger.debug(f"user_config_file = {user_config_file}")
180
+ if user_config_file is not None:
181
+ assert os.path.isfile(user_config_file), "DeepSpeed configuration file: {} is not an existing file".format(
182
+ user_config_file)
183
+ if os.path.exists(user_config_file):
184
+ return json.load(open(user_config_file, "r"), object_pairs_hook=dict_raise_error_on_duplicate_keys)
185
+
186
+ return None
187
+
188
+ def _get_resource_manager(self, active_resources):
189
+ """Initialize and return a resource manager
190
+
191
+ Args:
192
+ active_resources ([dict]): A dictionary of hostname and its slots (GPUs), e.g. {"worker-0": "0,1,2,3,4,5,6,7,8"}
193
+
194
+ Raises:
195
+ RuntimeError: raises the error if no GPU is available
196
+
197
+ Returns:
198
+ [ResourceManager]: A resource manager that schedules and runs autotuning experiments.
199
+ """
200
+ logger.info(f"active_resources = {active_resources}")
201
+
202
+ hosts = []
203
+ ngpus_per_node = 100
204
+ for hostname, slots in active_resources.items():
205
+ hosts.append(hostname)
206
+ ngpus_per_node = min(len(slots), ngpus_per_node)
207
+
208
+ assert ngpus_per_node > 0, "no gpu is available"
209
+
210
+ return ResourceManager(args=self.args,
211
+ hosts=hosts,
212
+ num_gpus_per_node=ngpus_per_node,
213
+ results_dir=self.results_dir,
214
+ exps_dir=self.exps_dir,
215
+ arg_mappings=self.autotuning_config.arg_mappings)
216
+
217
+ def _get_exp_resources(self, args):
218
+ """Get resource requirement for each autotuning experiment
219
+
220
+ Args:
221
+ args (dict): user args
222
+
223
+ Returns:
224
+ num_nodes, num_gpus: the number of gpus and number of nodes used in the autotuning experiments
225
+ """
226
+ if args.num_nodes > 0:
227
+ num_nodes = args.num_nodes
228
+ else:
229
+ num_nodes = len(self.rm.nodes)
230
+
231
+ if args.num_gpus > 0:
232
+ num_gpus = args.num_gpus
233
+ else:
234
+ num_gpus = self.rm.num_gpus_per_node
235
+
236
+ return num_nodes, num_gpus
237
+
238
+ def metric(self):
239
+ return self.autotuning_config.metric
240
+
241
+ def fast_enabled(self):
242
+ return self.autotuning_config.fast
243
+
244
+ def max_train_batch_size(self):
245
+ return self.autotuning_config.max_train_batch_size
246
+
247
+ def mp_size(self):
248
+ return self.autotuning_config.mp_size
249
+
250
+ def max_train_micro_batch_size_per_gpu(self):
251
+ if self.max_train_batch_size() and self.max_train_batch_size(
252
+ ) > 0: # if the user specifies a max_train_batch_size
253
+ max_train_micro_batch_size = self.max_train_batch_size() * self.mp_size() // (
254
+ self.exp_num_gpus * self.exp_num_nodes) # gradient accumulation steps >=1
255
+ return min(self.autotuning_config.max_train_micro_batch_size_per_gpu, max_train_micro_batch_size)
256
+ else:
257
+ return self.autotuning_config.max_train_micro_batch_size_per_gpu
258
+
259
+ def min_train_micro_batch_size_per_gpu(self):
260
+ return self.autotuning_config.min_train_micro_batch_size_per_gpu
261
+
262
+ def num_tuning_micro_batch_sizes(self):
263
+ return self.autotuning_config.num_tuning_micro_batch_sizes
264
+
265
+ def fp16_enabled(self):
266
+ if FP16 in self.user_config.keys():
267
+ return self.user_config[FP16].get(FP16_ENABLED, FP16_ENABLED_DEFAULT)
268
+ else:
269
+ return False
270
+
271
+ def get_gpu_memory_info(self):
272
+ return get_accelerator().total_memory()
273
+
274
+ def get_activation_memory_per_gpu(self):
275
+ if self.model_info and "activation_mem_per_gpu" in self.model_info:
276
+ return self.model_info["activation_mem_per_gpu"]
277
+
278
+ def get_instantiation_memory_required_per_gpu(self, zero_stage):
279
+ num_params = self.get_model_num_params()
280
+ total_gpus = self.exp_num_nodes * self.exp_num_gpus
281
+ fp16_enabled = self.fp16_enabled()
282
+
283
+ if not num_params:
284
+ return 0
285
+ # assume the model uses Adam optimizer
286
+ # ZeroStageEnum.disabled:
287
+ params_mem = num_params * (2 if fp16_enabled else 4)
288
+ gradients_mem = num_params * (2 if fp16_enabled else 4)
289
+ optimizer_mem = num_params * (16 if fp16_enabled else 8)
290
+
291
+ if zero_stage >= ZeroStageEnum.optimizer_states:
292
+ optimizer_mem = optimizer_mem / total_gpus
293
+
294
+ if zero_stage >= ZeroStageEnum.gradients:
295
+ gradients_mem = gradients_mem / total_gpus
296
+
297
+ if zero_stage >= ZeroStageEnum.weights:
298
+ params_mem = params_mem / total_gpus
299
+
300
+ mem_per_gpu = (params_mem + gradients_mem + optimizer_mem) / self.mp_size()
301
+
302
+ return mem_per_gpu
303
+
304
+ def _generate_experiments(self, tuning_space, max_train_batch_size_per_gpu):
305
+ """Generates a list of autotuning experiments given a tuning_space.
306
+ The corresponding parameter values are replaced by user-defined values in the DeepSpeed configuration file.
307
+ Args:
308
+ tuning_space ([dict]): A DeepSpeed configuration dictionary where a value can be a list (called a tuning parameter). For example,
309
+ {
310
+ "zero_optimization": {
311
+ "stage": 1,
312
+ "reduce_bucket_size": [5e7,
313
+ 5e8,
314
+ 1e9],
315
+ "allgather_bucket_size": [5e7,
316
+ 5e8,
317
+ 1e9],
318
+ }
319
+ }
320
+ reduce_bucket_size and allgather_bucket_size are the tuning parameters in this tuning space.
321
+ Returns:
322
+ [list]: a list of experiments generated by taking combinations of values of the tuning space. The above tuning space generates 3*3 = 9 experiments if the user DeepSpeed configuration file does not overwrite the two tuning parameters or define more tuning parameters.
323
+ """
324
+ exps = []
325
+
326
+ # each zero stage uses a different template configuration file
327
+ config_zero = tuning_space.get(ZERO_OPTIMIZATION, {})
328
+ stage = config_zero.get(ZERO_OPTIMIZATION_STAGE, ZERO_OPTIMIZATION_STAGE_DEFAULT)
329
+ template_config = {}
330
+ if stage == 0:
331
+ template_path = DEFAULT_TEMPLATE_PATH_ZERO_0
332
+ template_config = hjson.load(open(template_path, 'r'))
333
+ prefix = "z0_"
334
+
335
+ elif stage == 1:
336
+ template_path = DEFAULT_TEMPLATE_PATH_ZERO_1
337
+ template_config = hjson.load(open(template_path, 'r'))
338
+ prefix = "z1_"
339
+
340
+ elif stage == 2:
341
+ template_path = DEFAULT_TEMPLATE_PATH_ZERO_2
342
+ template_config = hjson.load(open(template_path, 'r'))
343
+ prefix = "z2_"
344
+
345
+ elif stage == 3:
346
+ template_path = DEFAULT_TEMPLATE_PATH_ZERO_3
347
+ template_config = hjson.load(open(template_path, 'r'))
348
+ model_info = self.model_info
349
+ if model_info and "hidden_size" in model_info:
350
+ hs = model_info["hidden_size"]
351
+ template_config[ZERO_OPTIMIZATION]['reduce_bucket_size'] = hs * hs
352
+ template_config[ZERO_OPTIMIZATION]['stage3_prefetch_bucket_size'] = 0.9 * hs * hs
353
+ template_config[ZERO_OPTIMIZATION]['stage3_param_persistence_threshold'] = 10 * hs
354
+ prefix = "z3_"
355
+ else:
356
+ return exps
357
+
358
+ # replace the corresponding parameter values if the user specifies them in the DeepSpeed configuration file
359
+ replace_dict(tuning_space, self.user_config, [ZERO_OPTIMIZATION, TRAIN_MICRO_BATCH_SIZE_PER_GPU])
360
+
361
+ logger.debug(f"tuning_space = {json.dumps(tuning_space)}")
362
+
363
+ all_configs = get_all_configs(tuning_space, ignore_keys=["optimizer"])
364
+
365
+ tuning_keys = get_tuning_keys(tuning_space)
366
+
367
+ logger.debug(f"tuning_keys = {tuning_keys}")
368
+
369
+ logger.debug(f"before pruning total configs = {len(all_configs)}")
370
+
371
+ pruned_list = prune_configs(all_configs)
372
+
373
+ logger.debug(f"after pruning total configs = {len(pruned_list)}")
374
+
375
+ for config in pruned_list:
376
+ exp_config = copy.deepcopy(template_config)
377
+ # fill the template with the expr config
378
+ replace_dict(exp_config, config)
379
+
380
+ # if the config does not use offloading, remove the offloading section
381
+ config_zero = config.get(ZERO_OPTIMIZATION, None)
382
+ if config_zero:
383
+ if OFFLOAD_OPTIMIZER not in config_zero and OFFLOAD_OPTIMIZER in exp_config[ZERO_OPTIMIZATION]:
384
+ del exp_config[ZERO_OPTIMIZATION][OFFLOAD_OPTIMIZER]
385
+ if OFFLOAD_PARAM not in config_zero and OFFLOAD_PARAM in exp_config[ZERO_OPTIMIZATION]:
386
+ del exp_config[ZERO_OPTIMIZATION][OFFLOAD_PARAM]
387
+ # set gradient accumulation steps according to max_train_batch_size_per_gpu
388
+ mbs = exp_config[TRAIN_MICRO_BATCH_SIZE_PER_GPU]
389
+ gas = max_train_batch_size_per_gpu // mbs
390
+ exp_config[GRADIENT_ACCUMULATION_STEPS] = gas
391
+ exp_config[TRAIN_BATCH_SIZE] = mbs * gas * \
392
+ self.exp_num_gpus * self.exp_num_nodes // self.mp_size()
393
+ exp = {}
394
+ # generate the expr name
395
+ exp_name = canonical_name(exp_config, tuning_keys, prefix)
396
+ exp['name'] = exp_name
397
+ exp[DS_CONFIG] = exp_config
398
+ exp['num_gpus'] = self.exp_num_gpus
399
+ exp['num_nodes'] = self.exp_num_nodes
400
+ exps.append(exp)
401
+
402
+ return exps
403
+
404
+ def tune(self):
405
+ """ Tunes Zero stages, micro batch size per GPU, and other Zero configurations. Performance metrics of different tuning spaces are recorded in self.records.
406
+ """
407
+ if has_mlflow:
408
+ self.mlflow_parent_id = os.environ['MLFLOW_RUN_ID']
409
+ mlflow.start_run(run_id=self.mlflow_parent_id)
410
+
411
+ self.start_time = time.time()
412
+ if self.fast_enabled():
413
+ logger.info(f"Fast mode is enabled. Tuning micro batch size only.")
414
+
415
+ # model info profile run with DEFAULT_MIN_MEM_CONFIG
416
+ model_info = self.model_info_profile_run()
417
+ if model_info:
418
+ self.model_info = model_info
419
+ else:
420
+ return
421
+
422
+ logger.info(f"The model has {number_to_string(self.get_model_num_params())} parameters.")
423
+
424
+ self.gpu_mem = self.get_gpu_memory_info()
425
+ logger.info(f"Memory per GPU in the system is {memory_to_string(self.gpu_mem, postfix='B')}.")
426
+
427
+ self.activation_mem = self.get_activation_memory_per_gpu()
428
+ logger.info(
429
+ f"The model requires at least {memory_to_string(self.activation_mem, postfix='B')} activation memory for micro batch size 1."
430
+ )
431
+
432
+ stage = self.user_config.get(ZERO_OPTIMIZATION, {}).get(ZERO_OPTIMIZATION_STAGE, 0)
433
+
434
+ user_zero_stages = [stage] if not isinstance(stage, list) else stage
435
+ logger.info(f"User-defined zero stages are {stage}.")
436
+
437
+ mbs = 0
438
+ max_mbs = 0
439
+ metric_val = 0
440
+
441
+ required_gpu_mem = self.get_instantiation_memory_required_per_gpu(ZeroStageEnum.disabled) + self.activation_mem
442
+ if self.gpu_mem > required_gpu_mem:
443
+ if "all" in user_zero_stages or ZeroStageEnum.disabled in user_zero_stages:
444
+ logger.info(
445
+ f"The model might be runable with ZERO 0 (which requires at least {memory_to_string(required_gpu_mem, postfix='B')} memory with mbs = 1), adding DEFAULT_TUNING_SPACE_ZERO_0 to the global tuning space"
446
+ )
447
+ next_max_mbs, next_mbs, next_metric_val = self.tune_space(DEFAULT_TUNING_SPACE_ZERO_0)
448
+ if next_mbs > mbs:
449
+ mbs = next_mbs
450
+ max_mbs = next_max_mbs
451
+ metric_val = next_metric_val
452
+ if has_mlflow:
453
+ mlflow.log_metric(f"z0{self.metric()}", next_metric_val)
454
+ else:
455
+ logger.info(
456
+ f"The model is not runable with ZERO stage {ZeroStageEnum.disabled} (which requires at least {memory_to_string(required_gpu_mem, postfix='B')} memory with mbs = 1)"
457
+ )
458
+
459
+ required_gpu_mem = self.get_instantiation_memory_required_per_gpu(
460
+ ZeroStageEnum.optimizer_states) + self.activation_mem
461
+ if self.gpu_mem > required_gpu_mem:
462
+ if "all" in user_zero_stages or ZeroStageEnum.optimizer_states in user_zero_stages:
463
+ logger.info(
464
+ f"The model might be runable with ZERO 1 (which requires at least {memory_to_string(required_gpu_mem, postfix='B')} memory), adding DEFAULT_TUNING_SPACE_ZERO_1 to the global tuning space"
465
+ )
466
+ next_max_mbs, next_mbs, next_metric_val = self.tune_space(DEFAULT_TUNING_SPACE_ZERO_1,
467
+ prev_max_mbs=max_mbs,
468
+ prev_best_mbs=mbs,
469
+ prev_best_metric_val=metric_val)
470
+ if next_mbs > mbs:
471
+ mbs = next_mbs
472
+ max_mbs = next_max_mbs
473
+ metric_val = next_metric_val
474
+ if has_mlflow:
475
+ mlflow.log_metric(f"z1{self.metric()}", next_metric_val)
476
+ else:
477
+ logger.info(
478
+ f"The model is not runable with ZERO stage {ZeroStageEnum.optimizer_states} (which requires at least {memory_to_string(required_gpu_mem, postfix='B')} memory with mbs = 1)"
479
+ )
480
+
481
+ required_gpu_mem = self.get_instantiation_memory_required_per_gpu(
482
+ ZeroStageEnum.gradients) + self.activation_mem
483
+ if self.gpu_mem > required_gpu_mem:
484
+ if "all" in user_zero_stages or ZeroStageEnum.gradients in user_zero_stages:
485
+ logger.info(
486
+ f"The model might be runable with ZERO 2 (which requires at least {memory_to_string(required_gpu_mem, postfix='B')} memory), adding DEFAULT_TUNING_SPACE_ZERO_2 to the global tuning space"
487
+ )
488
+ next_max_mbs, next_mbs, next_metric_val = self.tune_space(DEFAULT_TUNING_SPACE_ZERO_2,
489
+ prev_max_mbs=max_mbs,
490
+ prev_best_mbs=mbs,
491
+ prev_best_metric_val=metric_val)
492
+ if next_mbs > mbs:
493
+ mbs = next_mbs
494
+ max_mbs = next_max_mbs
495
+ metric_val = next_metric_val
496
+ if has_mlflow:
497
+ mlflow.log_metric(f"z2{self.metric()}", next_metric_val)
498
+ else:
499
+ logger.info(
500
+ f"The model is not runable with ZERO stage {ZeroStageEnum.gradients} (which requires at least {memory_to_string(required_gpu_mem, postfix='B')} memory with mbs = 1)"
501
+ )
502
+
503
+ required_gpu_mem = self.get_instantiation_memory_required_per_gpu(ZeroStageEnum.weights) + self.activation_mem
504
+ if self.gpu_mem > required_gpu_mem:
505
+ if "all" in user_zero_stages or ZeroStageEnum.weights in user_zero_stages:
506
+ logger.info(
507
+ f"The model might be runable with ZERO 3 (which requires at least {memory_to_string(required_gpu_mem, postfix='B')} memory), adding DEFAULT_TUNING_SPACE_ZERO_3 to the global tuning space"
508
+ )
509
+ _, _, next_metric_val = self.tune_space(DEFAULT_TUNING_SPACE_ZERO_3,
510
+ prev_max_mbs=max_mbs,
511
+ prev_best_mbs=mbs,
512
+ prev_best_metric_val=metric_val)
513
+ if has_mlflow:
514
+ mlflow.log_metric(f"z3{self.metric()}", next_metric_val)
515
+ else:
516
+ logger.info(
517
+ f"The model has {self.get_model_num_params()} parameters and requires at least {memory_to_string(required_gpu_mem, postfix='B')} memory per GPU with DeepSpeed Zero stage {ZeroStageEnum.weights} optimization. Memory per GPU in system is {memory_to_string(self.gpu_mem)}. No tuning is performed."
518
+ )
519
+ return
520
+ if has_mlflow:
521
+ mlflow.end_run()
522
+
523
+ def tune_space(self, tuning_space, prev_max_mbs=0, prev_best_mbs=0, prev_best_metric_val=0):
524
+ config_zero = tuning_space.get(ZERO_OPTIMIZATION, {})
525
+ stage = config_zero.get(ZERO_OPTIMIZATION_STAGE, None)
526
+ tuning_space_name = TUNING_MICRO_BATCH_SIZE_PREFIX + str(stage)
527
+ tuning_micro_batch_sizes = []
528
+ max_train_batch_size_per_gpu = 0
529
+ tuning_micro_batch_sizes_overwritten = False
530
+
531
+ # calculate max micro batch size using gpu memory, model instantiation memory and activation memory
532
+ # calculated_max_micro_batch_size = (memory_per_gpu - instantiation_memory) // activation_memory_micro_batch_size_1
533
+ calculated_max_micro_batch_size = int(
534
+ self.gpu_mem - self.get_instantiation_memory_required_per_gpu(stage)) // self.activation_mem
535
+ logger.info(
536
+ f"Start tuning for space {tuning_space_name}, calculated_max_micro_batch_size = {calculated_max_micro_batch_size}"
537
+ )
538
+
539
+ if calculated_max_micro_batch_size < prev_max_mbs:
540
+ logger.info(f"No need to tune Zero stage {stage}. End tuning for space {tuning_space_name}")
541
+ return 0, 0, 0
542
+
543
+ if TRAIN_MICRO_BATCH_SIZE_PER_GPU in self.user_config and isinstance(
544
+ self.user_config[TRAIN_MICRO_BATCH_SIZE_PER_GPU], list):
545
+ # user-specified micro batch size per gpu is a list which overwrites the default tuning behavior
546
+ tuning_micro_batch_sizes = [
547
+ s for s in self.user_config[TRAIN_MICRO_BATCH_SIZE_PER_GPU] if isinstance(s, int)
548
+ ]
549
+ gas = self.get_gas_from_user_config()
550
+ min_micro_batch_size = min(tuning_micro_batch_sizes)
551
+ max_micro_batch_size = max(tuning_micro_batch_sizes)
552
+ max_train_batch_size_per_gpu = max_micro_batch_size * gas
553
+ tuning_micro_batch_sizes_overwritten = True
554
+ else:
555
+ # auto-detects the list of micro batch sizes to tune
556
+ min_micro_batch_size, max_micro_batch_size = self.get_min_max_micro_batch_size(
557
+ stage, prev_max_mbs, calculated_max_micro_batch_size)
558
+
559
+ if max_micro_batch_size < prev_max_mbs:
560
+ logger.info(f"No need to tune Zero stage {stage}. End tuning for space {tuning_space_name}")
561
+ return 0, 0, 0
562
+
563
+ tuning_micro_batch_sizes, max_train_batch_size_per_gpu = self.get_tuning_micro_batch_size_list(
564
+ min_micro_batch_size,
565
+ max_micro_batch_size,
566
+ num_tuning_micro_batch_sizes=self.num_tuning_micro_batch_sizes())
567
+
568
+ logger.info(
569
+ f"tuning_micro_batch_sizes = {tuning_micro_batch_sizes}, max_train_batch_size_per_gpu = {max_train_batch_size_per_gpu}"
570
+ )
571
+
572
+ # return if the tuning_micro_batch_sizes list is empty
573
+ if not tuning_micro_batch_sizes:
574
+ logger.info(f"End tuning for space {tuning_space_name}")
575
+ return 0, 0, 0
576
+
577
+ # tune micro batch sizes and gradient accumulation steps given max_train_batch_size_per_gpu
578
+ tuning_micro_batch_sizes = self.run_tuning_micro_batch_sizes(tuning_micro_batch_sizes,
579
+ max_train_batch_size_per_gpu,
580
+ min_micro_batch_size, stage,
581
+ tuning_micro_batch_sizes_overwritten)
582
+
583
+ fast_best_record = self.get_best_space_record(tuning_space_name)
584
+ fast_best_metric_val = fast_best_record[1] if fast_best_record else 0
585
+ fast_best_mbs = fast_best_record[0][DS_CONFIG][TRAIN_MICRO_BATCH_SIZE_PER_GPU] if fast_best_record else 0
586
+ logger.info(f"fast_best_mbs = {fast_best_mbs}, name = {fast_best_record[0]['name']}")
587
+
588
+ if self.fast_enabled() or stage == 0:
589
+ logger.info(f"End tuning for space: {tuning_space_name}")
590
+ return max_micro_batch_size, fast_best_mbs, fast_best_metric_val
591
+
592
+ # if the best metric or the micro batch size for that best metric in the current Zero stage after tuning micro batch size is less than the corresponding value in the previous Zero stage, return, do not tune other Zero configuration parameters
593
+ if stage > 0:
594
+ if fast_best_mbs <= prev_best_mbs or fast_best_metric_val < prev_best_metric_val:
595
+ logger.info(
596
+ f"End tuning for space: {tuning_space_name}. No need to tune other Zero configuration parameters.")
597
+ return max_micro_batch_size, fast_best_mbs, fast_best_metric_val
598
+
599
+ tuning_space[TRAIN_MICRO_BATCH_SIZE_PER_GPU] = tuning_micro_batch_sizes
600
+ tuning_space_name = canonical_name(tuning_space,
601
+ tuning_keys=get_tuning_keys(tuning_space),
602
+ prefix="z" + str(stage) + "_",
603
+ omit_val=True)
604
+
605
+ logger.info(f'Tuning space is {tuning_space}')
606
+ logger.info(f'Tuning space name is {tuning_space_name}')
607
+
608
+ exps = self._generate_experiments(tuning_space, max_train_batch_size_per_gpu)
609
+
610
+ logger.info(f'Tuner type is {self.autotuning_config.tuner_type}')
611
+ if self.autotuning_config.tuner_type == AUTOTUNING_TUNER_MODELBASED:
612
+ t = ModelBasedTuner(exps, self.rm, self.metric(), tuning_space)
613
+ elif self.autotuning_config.tuner_type == AUTOTUNING_TUNER_RANDOM:
614
+ t = RandomTuner(exps, self.rm, self.metric())
615
+ else:
616
+ t = GridSearchTuner(exps, self.rm, self.metric())
617
+
618
+ sample_size = len(self.rm.nodes) * self.rm.num_gpus_per_node // (self.exp_num_gpus * self.exp_num_nodes)
619
+ num_exps = t.tune(sample_size=sample_size,
620
+ n_trials=self.autotuning_config.tuner_num_trials,
621
+ early_stopping=self.autotuning_config.tuner_early_stopping)
622
+ exp = t.best_exp
623
+ metric_val = t.best_metric_val
624
+ if exp:
625
+ self.update_records(tuning_space_name, exp, metric_val, num_exps)
626
+
627
+ full_best_record = self.get_best_space_record(tuning_space_name)
628
+ full_best_metric_val = full_best_record[1] if full_best_record else -1
629
+
630
+ if full_best_metric_val > fast_best_metric_val:
631
+ best_metric_val = full_best_metric_val
632
+ best_mbs = full_best_record[0][DS_CONFIG][TRAIN_MICRO_BATCH_SIZE_PER_GPU] if full_best_record else -1
633
+ else:
634
+ best_metric_val = fast_best_metric_val
635
+ best_mbs = fast_best_mbs
636
+
637
+ logger.info(f"End tuning for space: {tuning_space_name}")
638
+ return max_micro_batch_size, best_mbs, best_metric_val
639
+
640
+ def get_plateau_mbs(self, tuning_space_name):
641
+ if tuning_space_name not in self.records:
642
+ return 0
643
+ space_records = self.records[tuning_space_name]
644
+ sorted_space_records = sorted(space_records, key=lambda x: x[0][DS_CONFIG][TRAIN_MICRO_BATCH_SIZE_PER_GPU])
645
+ prev_metric_val = None
646
+ prev_micro_batch_size = 0
647
+ for (exp, metric_val, _) in sorted_space_records:
648
+ if prev_metric_val:
649
+ if metric_val < prev_metric_val:
650
+ break
651
+ if (metric_val >= prev_metric_val
652
+ and (metric_val - prev_metric_val) / prev_metric_val < METRIC_PERCENT_DIFF_CONST):
653
+ break
654
+ prev_metric_val = metric_val
655
+ prev_micro_batch_size = exp[DS_CONFIG][TRAIN_MICRO_BATCH_SIZE_PER_GPU]
656
+ plateau_mbs = prev_micro_batch_size
657
+ return plateau_mbs
658
+
659
+ def get_model_num_params(self):
660
+ if self.model_info and "num_params" in self.model_info:
661
+ return self.model_info["num_params"]
662
+
663
+ def model_info_profile_run(self):
664
+ """Does a model information profiling experiment that collects the number of model parameters and activation memory.\
665
+ The experiment produces a "profile_model_info" folder under self.results_dir.
666
+ Returns:
667
+ [dict]: a model information dictionary, e.g., {"num_params": 335144976, "trainable_num_params": 335144976, "activation_mem_per_gpu": 324358144, "rank": 0}
668
+ """
669
+ logger.info("Starting model info profile run.")
670
+ model_info = self.autotuning_config.model_info
671
+ if model_info and MODEL_INFO_NUM_PARAMS in model_info:
672
+ return model_info
673
+
674
+ ds_config = copy.deepcopy(self.user_config)
675
+ replace_dict(ds_config, DEFAULT_MIN_MEM_CONFIG)
676
+
677
+ model_info_path = os.path.join(self.results_dir, "profile_model_info", "model_info.json")
678
+ ds_config[AUTOTUNING] = {"enabled": True, "model_info_path": model_info_path, "model_info": {"profile": True}}
679
+
680
+ exp_config = {}
681
+ exp_name = "profile_model_info"
682
+ exp_config['name'] = exp_name
683
+ exp_config[DS_CONFIG] = ds_config
684
+ exp_config['num_gpus'] = self.exp_num_gpus
685
+ exp_config['num_nodes'] = self.exp_num_nodes
686
+ exp_config['hostfile'] = self.args.hostfile
687
+ exp_path = os.path.join(self.exps_dir, f'{exp_name}.json')
688
+
689
+ with open(exp_path, 'w', buffering=BUFSIZE) as fd:
690
+ json.dump(exp_config, fd)
691
+ fd.flush()
692
+ os.fsync(fd)
693
+
694
+ self.rm.schedule_experiments([exp_path])
695
+ self.rm.run()
696
+
697
+ for exp_id, (exp_json, err) in self.rm.finished_experiments.items():
698
+ self.rm.clear()
699
+ if err:
700
+ logger.error(f"The model is not runnable with DeepSpeed with error = {err}")
701
+ return None
702
+
703
+ if os.path.exists(model_info_path):
704
+ with open(model_info_path, 'r') as f:
705
+ model_info = hjson.load(f)
706
+ return model_info
707
+
708
+ def update_records(self, space_name, exp, metric_val, num_exps):
709
+ if space_name not in self.records:
710
+ self.records[space_name] = [(exp, metric_val, num_exps)]
711
+ else:
712
+ self.records[space_name].append((exp, metric_val, num_exps))
713
+
714
+ def get_best_space_record(self, space_name):
715
+ if space_name not in self.records:
716
+ return None
717
+ space_records = self.records[space_name]
718
+ best_space_record = None
719
+ space_num_exps = 0
720
+ for (exp, metric_val, num_exps) in space_records:
721
+ space_num_exps += num_exps
722
+ if best_space_record is None or metric_val > best_space_record[1]:
723
+ best_space_record = (exp, metric_val)
724
+ if best_space_record:
725
+ best_space_record = best_space_record + (space_num_exps, )
726
+ return best_space_record
727
+
728
+ def get_best_space_records(self):
729
+ best_space_records = {}
730
+ global_best_record = None
731
+ for space_name, space_records in self.records.items():
732
+ best_space_record = self.get_best_space_record(space_name)
733
+ if best_space_record:
734
+ best_space_records[space_name] = best_space_record
735
+ if not global_best_record or best_space_record[1] > global_best_record[1]:
736
+ global_best_record = best_space_record
737
+ if global_best_record:
738
+ best_space_records[GLOBAL_TUNING_SPACE] = global_best_record
739
+ return best_space_records
740
+
741
+ def run_tuning_micro_batch_sizes(self, tuning_micro_batch_sizes, max_train_batch_size_per_gpu,
742
+ min_micro_batch_size, stage, tuning_micro_batch_sizes_overwritten):
743
+ assert tuning_micro_batch_sizes, "the tuning micro batch size list is empty"
744
+ tuning_micro_batch_sizes.sort()
745
+ max_micro_batch_size = tuning_micro_batch_sizes[-1]
746
+ max_micro_batch_size_metric_val = 0
747
+
748
+ ds_config = get_first_config(self.user_config)
749
+ ds_config[ZERO_OPTIMIZATION] = {ZERO_OPTIMIZATION_STAGE: stage}
750
+ tuning_space_name = TUNING_MICRO_BATCH_SIZE_PREFIX + str(stage)
751
+
752
+ exp_paths = []
753
+ for mbs in tuning_micro_batch_sizes:
754
+ ds_config[TRAIN_MICRO_BATCH_SIZE_PER_GPU] = mbs
755
+ gas = max_train_batch_size_per_gpu // mbs
756
+ ds_config[GRADIENT_ACCUMULATION_STEPS] = gas
757
+ ds_config[TRAIN_BATCH_SIZE] = mbs * gas * \
758
+ self.exp_num_gpus * self.exp_num_nodes // self.mp_size()
759
+ exp_name = tuning_space_name + "_gas" + str(gas) + "_tmbspg" + str(mbs)
760
+ exp_config = {}
761
+ exp_config['name'] = exp_name
762
+ exp_config[DS_CONFIG] = ds_config
763
+ exp_config['num_gpus'] = self.exp_num_gpus
764
+ exp_config['num_nodes'] = self.exp_num_nodes
765
+ exp_config['hostfile'] = self.args.hostfile
766
+ exp_path = os.path.join(self.exps_dir, f'{exp_name}.json')
767
+
768
+ with open(exp_path, 'w', buffering=BUFSIZE) as fd:
769
+ json.dump(exp_config, fd)
770
+ fd.flush()
771
+ os.fsync(fd)
772
+ exp_paths.append(exp_path)
773
+
774
+ self.rm.schedule_experiments(exp_paths)
775
+ self.rm.run()
776
+
777
+ for exp_id, (exp, err) in self.rm.finished_experiments.items():
778
+ if exp:
779
+ metric_file = exp[DS_CONFIG][AUTOTUNING][AUTOTUNING_METRIC_PATH]
780
+ if os.path.exists(metric_file):
781
+
782
+ with open(metric_file, 'r') as f:
783
+ results = hjson.load(f)
784
+ metric_val = results[self.metric()]
785
+ self.update_records(tuning_space_name, exp, metric_val, 1)
786
+ if max_micro_batch_size == exp[DS_CONFIG][TRAIN_MICRO_BATCH_SIZE_PER_GPU]:
787
+ max_micro_batch_size_metric_val = metric_val
788
+ if has_mlflow:
789
+ os.environ.pop('MLFLOW_RUN_ID')
790
+ mlflow.start_run(nested=True, run_name=exp['name'])
791
+ for metric in results:
792
+ mlflow.log_metric(metric, results[metric])
793
+ mlflow.end_run()
794
+ os.environ['MLFLOW_RUN_ID'] = self.mlflow_parent_id
795
+ else:
796
+ self.update_records(tuning_space_name, exp, 0, 1)
797
+ else:
798
+ mbs = exp[DS_CONFIG][TRAIN_MICRO_BATCH_SIZE_PER_GPU]
799
+ logger.info(f"micro batch size = {mbs} was not run successfully")
800
+
801
+ self.rm.clear()
802
+
803
+ if tuning_micro_batch_sizes_overwritten:
804
+ return tuning_micro_batch_sizes
805
+
806
+ # in a auto-detected tuning_micro_batch_sizes list, max_micro_batch_size might not be performant as the memory consumption is close to max
807
+ # try smaller values while gas stays the same
808
+ # if finding a more performant mbs value, use it to replace max_micro_batch_size in the list
809
+ min_micro_batch_size_with_same_gas = (tuning_micro_batch_sizes[-2] +
810
+ 1) if len(tuning_micro_batch_sizes) > 1 else min_micro_batch_size
811
+
812
+ prev_best_metric_val = max_micro_batch_size_metric_val
813
+ prev_best_mbs = max_micro_batch_size
814
+
815
+ stride = (max_micro_batch_size - min_micro_batch_size_with_same_gas) // 3
816
+ if stride == 0:
817
+ stride = 1
818
+ for mbs in reversed(range(min_micro_batch_size_with_same_gas, max_micro_batch_size, stride)):
819
+ ds_config[TRAIN_MICRO_BATCH_SIZE_PER_GPU] = mbs
820
+ gas = max_train_batch_size_per_gpu // mbs
821
+ ds_config[GRADIENT_ACCUMULATION_STEPS] = gas
822
+ ds_config[TRAIN_BATCH_SIZE] = mbs * gas * \
823
+ self.exp_num_gpus * self.exp_num_nodes // self.mp_size()
824
+ exp_name = tuning_space_name + "_gas" + str(gas) + "_tmbspg" + str(mbs)
825
+ exp, metric_val = self.run_ds_config(ds_config, exp_name)
826
+
827
+ if metric_val:
828
+ with open(metric_file, 'r') as f:
829
+ results = hjson.load(f)
830
+ metric_val = results[self.metric()]
831
+ if has_mlflow:
832
+ os.environ.pop('MLFLOW_RUN_ID')
833
+ mlflow.start_run(nested=True, run_name=exp_name)
834
+ for metric in results:
835
+ mlflow.log_metric(metric, results[metric])
836
+ mlflow.end_run()
837
+ os.environ['MLFLOW_RUN_ID'] = self.mlflow_parent_id
838
+ self.update_records(tuning_space_name, exp, metric_val, 1)
839
+ if metric_val > prev_best_metric_val * (1 + METRIC_PERCENT_DIFF_CONST):
840
+ prev_best_metric_val = metric_val
841
+ prev_best_mbs = mbs
842
+ else:
843
+ break
844
+ else:
845
+ self.update_records(tuning_space_name, exp, 0, 1)
846
+ break
847
+ if prev_best_mbs != max_micro_batch_size:
848
+ tuning_micro_batch_sizes[-1] = prev_best_mbs
849
+ return tuning_micro_batch_sizes
850
+
851
+ def get_min_max_micro_batch_size(self, stage, min_micro_batch_size, calculated_max_micro_batch_size):
852
+ # get min and max micro batch size with gradient accumulation steps = 1
853
+ if min_micro_batch_size > calculated_max_micro_batch_size:
854
+ return -1, -1
855
+
856
+ used_micro_batch_sizes = []
857
+ tuning_space_name = TUNING_MICRO_BATCH_SIZE_PREFIX + str(stage)
858
+
859
+ ds_config = get_first_config(self.user_config)
860
+ ds_config[ZERO_OPTIMIZATION] = {ZERO_OPTIMIZATION_STAGE: stage}
861
+ gas = self.get_gas_from_user_config()
862
+ ds_config[GRADIENT_ACCUMULATION_STEPS] = gas
863
+
864
+ # search for the min micro batch size
865
+ if min_micro_batch_size < 1:
866
+ if TRAIN_MICRO_BATCH_SIZE_PER_GPU in self.user_config and isinstance(
867
+ self.user_config[TRAIN_MICRO_BATCH_SIZE_PER_GPU], int):
868
+ # user specifies train_micro_batch_size_per_gpu as an int
869
+ mbs = int(self.user_config[TRAIN_MICRO_BATCH_SIZE_PER_GPU])
870
+ else:
871
+ # user does not specify train_micro_batch_size_per_gpu or sets it to "auto" when using Hugging Face
872
+ val = self.get_val_from_user_args(TRAIN_MICRO_BATCH_SIZE_PER_GPU)
873
+ if val:
874
+ mbs = int(val)
875
+ else:
876
+ mbs = 1
877
+ assert mbs > 0, "The micro batch size per GPU must be greater than 0."
878
+ ds_config[TRAIN_MICRO_BATCH_SIZE_PER_GPU] = mbs
879
+ ds_config[GRADIENT_ACCUMULATION_STEPS] = gas
880
+ ds_config[TRAIN_BATCH_SIZE] = mbs * gas * \
881
+ self.exp_num_gpus * self.exp_num_nodes // self.mp_size()
882
+ exp_name = tuning_space_name + "_gas" + str(gas) + "_tmbspg" + str(mbs)
883
+ exp, metric_val = self.run_ds_config(ds_config, exp_name)
884
+ if metric_val:
885
+ self.update_records(tuning_space_name, exp, metric_val, 1)
886
+ used_micro_batch_sizes.append(mbs)
887
+ min_micro_batch_size = mbs
888
+ else:
889
+ self.update_records(tuning_space_name, exp, 0, 1)
890
+ logger.info(f"User-specified micro batch size per GPU {mbs} does not run")
891
+ if self.min_train_micro_batch_size_per_gpu() == mbs:
892
+ return -1, -1
893
+ mbs = self.min_train_micro_batch_size_per_gpu()
894
+ ds_config[TRAIN_MICRO_BATCH_SIZE_PER_GPU] = mbs
895
+ ds_config[GRADIENT_ACCUMULATION_STEPS] = gas
896
+ ds_config[TRAIN_BATCH_SIZE] = mbs * gas * \
897
+ self.exp_num_gpus * self.exp_num_nodes // self.mp_size()
898
+ exp_name = tuning_space_name + "_gas" + str(gas) + "_tmbspg" + str(mbs)
899
+ exp, metric_val = self.run_ds_config(ds_config, exp_name)
900
+ if not metric_val:
901
+ self.update_records(tuning_space_name, exp, 0, 1)
902
+ logger.info(f"min_train_micro_batch_size_per_gpu {mbs} is not runnable.")
903
+ return -1, -1
904
+ self.update_records(tuning_space_name, exp, metric_val, 1)
905
+ min_micro_batch_size = mbs
906
+ used_micro_batch_sizes.append(mbs)
907
+ else:
908
+ ds_config[TRAIN_MICRO_BATCH_SIZE_PER_GPU] = min_micro_batch_size
909
+ ds_config[GRADIENT_ACCUMULATION_STEPS] = gas
910
+ ds_config[TRAIN_BATCH_SIZE] = min_micro_batch_size * gas * \
911
+ self.exp_num_gpus * self.exp_num_nodes // self.mp_size()
912
+ exp_name = tuning_space_name + "_gas" + str(gas) + "_tmbspg" + str(min_micro_batch_size)
913
+ exp, metric_val = self.run_ds_config(ds_config, exp_name)
914
+ if metric_val:
915
+ self.update_records(tuning_space_name, exp, metric_val, 1)
916
+ used_micro_batch_sizes.append(min_micro_batch_size)
917
+ else:
918
+ self.update_records(tuning_space_name, exp, 0, 1)
919
+ return -1, -1
920
+
921
+ # search for the max micro batch size
922
+ max_micro_batch_size = min(calculated_max_micro_batch_size, self.max_train_micro_batch_size_per_gpu())
923
+ for mbs in [math.ceil(1.05 * max_micro_batch_size), max_micro_batch_size, int(0.95 * max_micro_batch_size)]:
924
+ if mbs > self.max_train_micro_batch_size_per_gpu():
925
+ continue
926
+ if mbs in used_micro_batch_sizes:
927
+ return min_micro_batch_size, mbs
928
+ ds_config[TRAIN_MICRO_BATCH_SIZE_PER_GPU] = mbs
929
+ ds_config[TRAIN_BATCH_SIZE] = mbs * gas * \
930
+ self.exp_num_gpus * self.exp_num_nodes // self.mp_size()
931
+ exp_name = tuning_space_name + "_gas" + str(gas) + "_tmbspg" + str(mbs)
932
+ exp, metric_val = self.run_ds_config(ds_config, exp_name)
933
+
934
+ if metric_val:
935
+ logger.info(f"mbs = {mbs} is found as max mbs")
936
+ self.update_records(tuning_space_name, exp, metric_val, 1)
937
+ used_micro_batch_sizes.append(mbs)
938
+ return min_micro_batch_size, mbs
939
+ else:
940
+ self.update_records(tuning_space_name, exp, 0, 1)
941
+
942
+ space_records = self.records[tuning_space_name] if tuning_space_name in self.records else []
943
+ if space_records:
944
+ prev_idx = min(range(len(space_records)),
945
+ key=lambda i: abs(space_records[i][0][DS_CONFIG][TRAIN_MICRO_BATCH_SIZE_PER_GPU] -
946
+ min_micro_batch_size))
947
+ prev_metric_val = space_records[prev_idx][1]
948
+ else:
949
+ prev_metric_val = None
950
+
951
+ low = min_micro_batch_size
952
+ high = max_micro_batch_size
953
+ # binary search until low is the smallest micro batch size that OOMs.
954
+ while low <= high:
955
+ mid = int((low + high) // 2)
956
+ logger.debug(f"trying mbs = {mid}, low = {low}, high = {high}")
957
+ if mid not in used_micro_batch_sizes:
958
+ ds_config[TRAIN_MICRO_BATCH_SIZE_PER_GPU] = mid
959
+ ds_config[TRAIN_BATCH_SIZE] = mid * gas * \
960
+ self.exp_num_gpus * self.exp_num_nodes // self.mp_size()
961
+ exp_name = tuning_space_name + "_gas" + str(gas) + "_tmbspg" + str(mid)
962
+ exp, metric_val = self.run_ds_config(ds_config, exp_name)
963
+ if metric_val:
964
+ low = mid + 1
965
+ self.update_records(tuning_space_name, exp, metric_val, 1)
966
+ used_micro_batch_sizes.append(mid)
967
+ if prev_metric_val and ((metric_val - prev_metric_val) /
968
+ prev_metric_val) < METRIC_PERCENT_DIFF_CONST:
969
+ logger.info(f"performance plateaus at mbs = {low}")
970
+ break
971
+ prev_metric_val = metric_val
972
+ else:
973
+ self.update_records(tuning_space_name, exp, 0, 1)
974
+ high = mid - 1
975
+ else:
976
+ low = mid + 1
977
+ max_micro_batch_size = low - 1
978
+
979
+ logger.info(f"min_micro_batch_size = {min_micro_batch_size}, max_micro_batch_size = {max_micro_batch_size}.")
980
+
981
+ return min_micro_batch_size, max_micro_batch_size
982
+
983
+ def get_gas_from_user_config(self):
984
+ gas = 1
985
+ if GRADIENT_ACCUMULATION_STEPS in self.user_config:
986
+ gas_in_config = self.user_config[GRADIENT_ACCUMULATION_STEPS]
987
+ if isinstance(gas_in_config, int):
988
+ gas = gas_in_config
989
+ elif gas_in_config == "auto": # GRADIENT_ACCUMULATION_STEPS: "auto"
990
+ val = self.get_val_from_user_args(GRADIENT_ACCUMULATION_STEPS)
991
+ if val:
992
+ gas = int(val)
993
+ elif isinstance(gas_in_config, list):
994
+ logger.info(
995
+ f"Specifying a list of {GRADIENT_ACCUMULATION_STEPS} to tune is not supported. 1 would be used.")
996
+ assert gas > 0, "Gradient accumulation steps must be positive."
997
+ return gas
998
+
999
+ def get_val_from_user_args(self, ds_name):
1000
+ arg_mappings = self.autotuning_config.arg_mappings
1001
+ user_args = self.args.user_args
1002
+ if arg_mappings and ds_name in arg_mappings:
1003
+ arg_name = arg_mappings[ds_name]
1004
+ if arg_name in user_args:
1005
+ idx = user_args.index(arg_name)
1006
+ if user_args[idx + 1].isnumeric():
1007
+ return (user_args[idx + 1])
1008
+ return None
1009
+
1010
+ def get_tuning_micro_batch_size_list(self, min_micro_batch_size, max_micro_batch_size,
1011
+ num_tuning_micro_batch_sizes):
1012
+ """Get a list of micro batch sizes to tune based on min and max values, as well as the size of the list.
1013
+ Args:
1014
+ min_micro_batch_size ([int]): min micro batch size per GPU
1015
+ max_micro_batch_size ([int]): max micro batch size per GPU
1016
+ num_tuning_micro_batch_sizes (int): the number of items in the returned list
1017
+
1018
+ Returns:
1019
+ [list]: a list of micro batch sizes to tune.
1020
+ """
1021
+ if min_micro_batch_size <= 0 or max_micro_batch_size <= 0:
1022
+ logger.info(
1023
+ f"min_micro_batch_size = {min_micro_batch_size}, max_micro_batch_size = {max_micro_batch_size}")
1024
+ return [], 0
1025
+
1026
+ # NUM_GPUS=$(( ${NUM_WORKERS} * ${NUM_GPUS_PER_WORKER} ))
1027
+ # DP_SIZE=$(( ${NUM_GPUS} / (${PP_SIZE} * ${MP_SIZE}) ))
1028
+ # GRAD_ACC_STEPS=$(( ${TARGET_GLOBAL_BATCH_SIZE} / (${BATCH_SIZE} * ${DP_SIZE}) ))
1029
+ if self.max_train_batch_size() and self.max_train_batch_size(
1030
+ ) > 0: # if the user specifies a max_train_batch_size
1031
+ max_train_batch_size_per_gpu = self.max_train_batch_size() * self.mp_size() // (self.exp_num_gpus *
1032
+ self.exp_num_nodes)
1033
+ else:
1034
+ gas = self.get_gas_from_user_config()
1035
+ max_train_batch_size_per_gpu = max_micro_batch_size * gas // self.mp_size()
1036
+ logger.info(f"max_train_batch_size_per_gpu = {max_train_batch_size_per_gpu}")
1037
+ if min_micro_batch_size < max_micro_batch_size // 2:
1038
+ min_micro_batch_size = max_micro_batch_size // 2
1039
+
1040
+ # constant stride
1041
+ stride = (max_micro_batch_size - min_micro_batch_size) // num_tuning_micro_batch_sizes
1042
+ if stride == 0:
1043
+ stride = 1
1044
+ ls = []
1045
+ min_gas = max_train_batch_size_per_gpu // max_micro_batch_size
1046
+ # if gas is the same as min_gas, do not add mbs to the tuning list
1047
+ for mbs in range(min_micro_batch_size, max_micro_batch_size, stride):
1048
+ if max_train_batch_size_per_gpu // mbs != min_gas:
1049
+ ls.append(mbs)
1050
+ ls.append(max_micro_batch_size)
1051
+
1052
+ return ls, max_train_batch_size_per_gpu
1053
+
1054
+ def run_ds_config(self, ds_config, exp_name):
1055
+ exp_config = {}
1056
+ exp_config['name'] = exp_name
1057
+ exp_config[DS_CONFIG] = ds_config
1058
+ exp_config['num_gpus'] = self.exp_num_gpus
1059
+ exp_config['num_nodes'] = self.exp_num_nodes
1060
+ exp_config['hostfile'] = self.args.hostfile
1061
+ exp_path = os.path.join(self.exps_dir, f'{exp_name}.json')
1062
+
1063
+ logger.debug(f'run_ds_config exp_name = {exp_name}')
1064
+
1065
+ with open(exp_path, 'w', buffering=BUFSIZE) as fd:
1066
+ json.dump(exp_config, fd)
1067
+ fd.flush()
1068
+ os.fsync(fd)
1069
+ self.rm.schedule_experiments([exp_path])
1070
+ self.rm.run()
1071
+ exp, metric_val = self.rm.parse_results(self.metric())
1072
+ self.rm.clear()
1073
+ return exp, metric_val
1074
+
1075
+ def write_optimal_config(self):
1076
+ best_space_records = self.get_best_space_records()
1077
+ if GLOBAL_TUNING_SPACE not in best_space_records:
1078
+ return
1079
+ best_exp, best_metric_val, _ = best_space_records[GLOBAL_TUNING_SPACE]
1080
+ if best_exp:
1081
+ exp_dir = best_exp["result_dir"]
1082
+ cmd = None
1083
+ with open(os.path.join(exp_dir, "cmd.txt"), "r") as f:
1084
+ cmd = [str(i) for i in f.read().split()]
1085
+
1086
+ ds_config = hjson.load(open(os.path.join(exp_dir, "ds_config.json"), "r"))
1087
+ ds_config.pop(AUTOTUNING)
1088
+
1089
+ ds_config_path = os.path.join(self.results_dir, "ds_config_optimal.json")
1090
+ json.dump(ds_config, open(ds_config_path, "w"))
1091
+
1092
+ cmd_path = os.path.join(self.results_dir, "cmd_optimal.txt")
1093
+ with open(cmd_path, "w") as fd:
1094
+ fd.write(" ".join(cmd))
1095
+ fd.write("\n")
1096
+ fd.flush()
1097
+ self.optimal_cmd = cmd
1098
+ self.optimal_ds_config = ds_config
1099
+ logger.info(
1100
+ f"Wrote the optimal DeepSpeed configuration found by autotuning to {ds_config_path}, and the corresponding DeepSpeed command to {cmd_path}"
1101
+ )
1102
+
1103
+ def run_after_tuning(self):
1104
+ """ Launches the training with the optimal DeepSpeed configuration found through the autotuning process.
1105
+ "ds_config_optimal.json" describing the optimal DeepSpeed configuration as well the command used to launch training "cmd_optimal.txt" are saved to self.results_dir.
1106
+ """
1107
+ if self.optimal_cmd:
1108
+ result = subprocess.Popen(self.optimal_cmd)
1109
+ result.wait()
1110
+
1111
+ logger.info(f"Done running with the optimal DeepSpeed configuration using {self.optimal_cmd}")
1112
+ else:
1113
+ logger.info(f"No optimal DeepSpeed configuration found by autotuning.")
lib/python3.12/site-packages/deepspeed/autotuning/config.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ from deepspeed.runtime.config_utils import get_scalar_param, get_dict_param, DeepSpeedConfigObject
7
+ from deepspeed.autotuning.constants import *
8
+
9
+
10
+ class DeepSpeedAutotuningConfig(DeepSpeedConfigObject):
11
+
12
+ def __init__(self, param_dict):
13
+ super(DeepSpeedAutotuningConfig, self).__init__()
14
+
15
+ self.enabled = None
16
+ self.start_step = None
17
+ self.end_step = None
18
+ self.metric_path = None
19
+ self.arg_mappings = None
20
+ self.metric = None
21
+ self.model_info = None
22
+ self.results_dir = None
23
+ self.exps_dir = None
24
+ self.overwrite = None
25
+
26
+ if param_dict and AUTOTUNING in param_dict.keys():
27
+ autotuning_dict = param_dict[AUTOTUNING]
28
+ else:
29
+ autotuning_dict = {}
30
+
31
+ self._initialize(autotuning_dict)
32
+
33
+ def _initialize(self, autotuning_dict):
34
+ self.enabled = get_scalar_param(autotuning_dict, AUTOTUNING_ENABLED, AUTOTUNING_ENABLED_DEFAULT)
35
+
36
+ self.fast = get_scalar_param(autotuning_dict, AUTOTUNING_FAST, AUTOTUNING_FAST_DEFAULT)
37
+
38
+ self.results_dir = get_scalar_param(autotuning_dict, AUTOTUNING_RESULTS_DIR, AUTOTUNING_RESULTS_DIR_DEFAULT)
39
+ assert self.results_dir, "results_dir cannot be empty"
40
+ self.exps_dir = get_scalar_param(autotuning_dict, AUTOTUNING_EXPS_DIR, AUTOTUNING_EXPS_DIR_DEFAULT)
41
+ assert self.exps_dir, "exps_dir cannot be empty"
42
+ self.overwrite = get_scalar_param(autotuning_dict, AUTOTUNING_OVERWRITE, AUTOTUNING_OVERWRITE_DEFAULT)
43
+
44
+ self.start_profile_step = get_scalar_param(autotuning_dict, AUTOTUNING_START_PROFILE_STEP,
45
+ AUTOTUNING_START_PROFILE_STEP_DEFAULT)
46
+
47
+ self.end_profile_step = get_scalar_param(autotuning_dict, AUTOTUNING_END_PROFILE_STEP,
48
+ AUTOTUNING_END_PROFILE_STEP_DEFAULT)
49
+
50
+ self.metric = get_scalar_param(autotuning_dict, AUTOTUNING_METRIC, AUTOTUNING_METRIC_DEFAULT)
51
+
52
+ self.metric_path = get_scalar_param(autotuning_dict, AUTOTUNING_METRIC_PATH, AUTOTUNING_METRIC_PATH_DEFAULT)
53
+
54
+ self.tuner_type = get_scalar_param(autotuning_dict, AUTOTUNING_TUNER_TYPE, AUTOTUNING_TUNER_TYPE_DEFAULT)
55
+
56
+ self.tuner_early_stopping = get_scalar_param(autotuning_dict, AUTOTUNING_TUNER_EARLY_STOPPING,
57
+ AUTOTUNING_TUNER_EARLY_STOPPING_DEFAULT)
58
+
59
+ self.tuner_num_trials = get_scalar_param(autotuning_dict, AUTOTUNING_TUNER_NUM_TRIALS,
60
+ AUTOTUNING_TUNER_NUM_TRIALS_DEFAULT)
61
+
62
+ self.arg_mappings = get_dict_param(autotuning_dict, AUTOTUNING_ARG_MAPPINGS, AUTOTUNING_ARG_MAPPINGS_DEFAULT)
63
+
64
+ self.model_info = get_model_info_config(autotuning_dict)
65
+
66
+ self.model_info_path = get_scalar_param(autotuning_dict, AUTOTUNING_MODEL_INFO_PATH,
67
+ AUTOTUNING_MODEL_INFO_PATH_DEFAULT)
68
+ self.mp_size = get_scalar_param(autotuning_dict, AUTOTUNING_MP_SIZE, AUTOTUNING_MP_SIZE_DEFAULT)
69
+
70
+ self.max_train_batch_size = get_dict_param(autotuning_dict, AUTOTUNING_MAX_TRAIN_BATCH_SIZE,
71
+ AUTOTUNING_MAX_TRAIN_BATCH_SIZE_DEFAULT)
72
+
73
+ self.min_train_batch_size = get_dict_param(autotuning_dict, AUTOTUNING_MIN_TRAIN_BATCH_SIZE,
74
+ AUTOTUNING_MIN_TRAIN_BATCH_SIZE_DEFAULT)
75
+
76
+ self.max_train_micro_batch_size_per_gpu = get_dict_param(
77
+ autotuning_dict, AUTOTUNING_MAX_TRAIN_MICRO_BATCH_SIZE_PER_GPU,
78
+ AUTOTUNING_MAX_TRAIN_MICRO_BATCH_SIZE_PER_GPU_DEFAULT)
79
+
80
+ self.min_train_micro_batch_size_per_gpu = get_dict_param(
81
+ autotuning_dict, AUTOTUNING_MIN_TRAIN_MICRO_BATCH_SIZE_PER_GPU,
82
+ AUTOTUNING_MIN_TRAIN_MICRO_BATCH_SIZE_PER_GPU_DEFAULT)
83
+
84
+ self.num_tuning_micro_batch_sizes = get_dict_param(autotuning_dict, AUTOTUNING_NUM_TUNING_MICRO_BATCH_SIZES,
85
+ AUTOTUNING_NUM_TUNING_MICRO_BATCH_SIZES_DEFAULT)
86
+
87
+
88
+ def get_model_info_config(param_dict):
89
+ if MODEL_INFO in param_dict and param_dict[MODEL_INFO] is not None:
90
+ model_info_config = {}
91
+ for key, default_value in MODEL_INFO_KEY_DEFAULT_DICT.items():
92
+ model_info_config[key] = get_scalar_param(param_dict[MODEL_INFO], key, default_value)
93
+ return model_info_config
94
+ return None
95
+
96
+
97
+ def get_default_model_info_config():
98
+ return MODEL_INFO_KEY_DEFAULT_DICT
lib/python3.12/site-packages/deepspeed/autotuning/config_templates/template_zero0.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "zero_optimization": {
3
+ "stage": 0
4
+ }
5
+ }
lib/python3.12/site-packages/deepspeed/autotuning/config_templates/template_zero1.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "zero_optimization": {
3
+ "stage": 1,
4
+ "reduce_bucket_size": 5e8,
5
+ "allgather_bucket_size": 5e8
6
+ }
7
+ }
lib/python3.12/site-packages/deepspeed/autotuning/config_templates/template_zero2.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "zero_optimization": {
3
+ "stage": 2,
4
+ "allgather_partitions": true,
5
+ "allgather_bucket_size": 5e8,
6
+ "overlap_comm": false,
7
+ "reduce_scatter": true,
8
+ "reduce_bucket_size": 5e8,
9
+ "contiguous_gradients": false
10
+ }
11
+ }
lib/python3.12/site-packages/deepspeed/autotuning/config_templates/template_zero3.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "zero_optimization": {
3
+ "stage": 3,
4
+ "allgather_partitions": true,
5
+ "allgather_bucket_size": 5e8,
6
+ "overlap_comm": false,
7
+ "reduce_scatter": true,
8
+ "reduce_bucket_size": 5e8,
9
+ "contiguous_gradients": false,
10
+ "stage3_max_live_parameters": 1e9,
11
+ "stage3_max_reuse_distance": 1e9,
12
+ "stage3_prefetch_bucket_size": 5e8,
13
+ "stage3_param_persistence_threshold": 1e6,
14
+ "stage3_gather_16bit_weights_on_model_save": false,
15
+ "sub_group_size": 1e12
16
+ }
17
+ }
lib/python3.12/site-packages/deepspeed/autotuning/constants.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ #########################################
7
+ # autotuner implementation constants
8
+ #########################################
9
+
10
+ import os
11
+
12
+ DEFAULT_TEMPLATE_PATH_ZERO_0 = os.path.join(os.path.dirname(os.path.realpath(__file__)), "config_templates",
13
+ "template_zero0.json")
14
+ DEFAULT_TEMPLATE_PATH_ZERO_1 = os.path.join(os.path.dirname(os.path.realpath(__file__)), "config_templates",
15
+ "template_zero1.json")
16
+ DEFAULT_TEMPLATE_PATH_ZERO_2 = os.path.join(os.path.dirname(os.path.realpath(__file__)), "config_templates",
17
+ "template_zero2.json")
18
+ DEFAULT_TEMPLATE_PATH_ZERO_3 = os.path.join(os.path.dirname(os.path.realpath(__file__)), "config_templates",
19
+ "template_zero3.json")
20
+
21
+ METRIC_PERCENT_DIFF_CONST = 0.05
22
+ DS_CONFIG = "ds_config"
23
+ BUFSIZE = 1 # line buffer size for writing files
24
+
25
+ #########################################
26
+ # autotuner configuration constants
27
+ #########################################
28
+ # Autotuner. By default, this feature is not enabled.
29
+ # Users can configure in ds_config.json as below example:
30
+ AUTOTUNING_FORMAT = """
31
+ autotuner should be enabled as:
32
+ "session_params": {
33
+ "autotuning": {
34
+ "enabled": true,
35
+ "start_step": 5,
36
+ "end_step": 15
37
+ }
38
+ }
39
+ """
40
+
41
+ AUTOTUNING = "autotuning"
42
+
43
+ AUTOTUNING_ENABLED = "enabled"
44
+ AUTOTUNING_ENABLED_DEFAULT = False
45
+
46
+ AUTOTUNING_FAST = "fast"
47
+ AUTOTUNING_FAST_DEFAULT = True
48
+
49
+ AUTOTUNING_RESULTS_DIR = "results_dir"
50
+ AUTOTUNING_RESULTS_DIR_DEFAULT = "autotuning_results"
51
+
52
+ AUTOTUNING_EXPS_DIR = "exps_dir"
53
+ AUTOTUNING_EXPS_DIR_DEFAULT = "autotuning_exps"
54
+
55
+ AUTOTUNING_OVERWRITE = "overwrite"
56
+ AUTOTUNING_OVERWRITE_DEFAULT = True
57
+
58
+ AUTOTUNING_START_PROFILE_STEP = "start_profile_step"
59
+ AUTOTUNING_START_PROFILE_STEP_DEFAULT = 3
60
+
61
+ AUTOTUNING_END_PROFILE_STEP = "end_profile_step"
62
+ AUTOTUNING_END_PROFILE_STEP_DEFAULT = 5
63
+ AUTOTUNING_METRIC_PATH = "metric_path"
64
+ AUTOTUNING_METRIC_PATH_DEFAULT = None
65
+
66
+ AUTOTUNING_TUNER_TYPE = "tuner_type"
67
+ AUTOTUNING_TUNER_GRIDSEARCH = "gridsearch"
68
+ AUTOTUNING_TUNER_RANDOM = "random"
69
+ AUTOTUNING_TUNER_MODELBASED = "model_based"
70
+ AUTOTUNING_TUNER_TYPE_DEFAULT = AUTOTUNING_TUNER_GRIDSEARCH
71
+ AUTOTUNING_TUNER_EARLY_STOPPING = "tuner_early_stopping"
72
+ AUTOTUNING_TUNER_EARLY_STOPPING_DEFAULT = 5
73
+ AUTOTUNING_TUNER_NUM_TRIALS = "tuner_num_trials"
74
+ AUTOTUNING_TUNER_NUM_TRIALS_DEFAULT = 50
75
+
76
+ AUTOTUNING_ARG_MAPPINGS = "arg_mappings"
77
+ AUTOTUNING_ARG_MAPPINGS_DEFAULT = None
78
+
79
+ AUTOTUNING_MAX_TRAIN_BATCH_SIZE = "max_train_batch_size"
80
+ AUTOTUNING_MAX_TRAIN_BATCH_SIZE_DEFAULT = None
81
+ AUTOTUNING_MIN_TRAIN_BATCH_SIZE = "min_train_batch_size"
82
+ AUTOTUNING_MIN_TRAIN_BATCH_SIZE_DEFAULT = 1
83
+ AUTOTUNING_MAX_TRAIN_MICRO_BATCH_SIZE_PER_GPU = "max_train_micro_batch_size_per_gpu"
84
+ AUTOTUNING_MAX_TRAIN_MICRO_BATCH_SIZE_PER_GPU_DEFAULT = 1024
85
+ AUTOTUNING_MIN_TRAIN_MICRO_BATCH_SIZE_PER_GPU = "min_train_micro_batch_size_per_gpu"
86
+ AUTOTUNING_MIN_TRAIN_MICRO_BATCH_SIZE_PER_GPU_DEFAULT = 1
87
+ AUTOTUNING_NUM_TUNING_MICRO_BATCH_SIZES = "num_tuning_micro_batch_sizes"
88
+ AUTOTUNING_NUM_TUNING_MICRO_BATCH_SIZES_DEFAULT = 3
89
+
90
+ AUTOTUNING_MP_SIZE = "mp_size"
91
+ AUTOTUNING_MP_SIZE_DEFAULT = 1
92
+
93
+ AUTOTUNING_METRIC = "metric"
94
+ AUTOTUNING_METRIC_LATENCY = "latency"
95
+ AUTOTUNING_METRIC_THROUGHPUT = "throughput"
96
+ AUTOTUNING_METRIC_FLOPS = "flops"
97
+ AUTOTUNING_METRIC_FORWARD = "forward"
98
+ AUTOTUNING_METRIC_BACKWRAD = "flops"
99
+ AUTOTUNING_METRIC_STEPS = "step"
100
+ AUTOTUNING_METRIC_DEFAULT = AUTOTUNING_METRIC_THROUGHPUT
101
+
102
+ #########################################
103
+ # MODEL INFO
104
+ #########################################
105
+ AUTOTUNING_MODEL_INFO_PATH = "model_info_path"
106
+ AUTOTUNING_MODEL_INFO_PATH_DEFAULT = None
107
+
108
+ MODEL_INFO_FORMAT = '''
109
+ "model_info": {
110
+ "num_params": 1000000000,
111
+ "hidden_size": 10,
112
+ "num_layers": 12,
113
+ }
114
+ '''
115
+ MODEL_INFO = "model_info"
116
+ MODEL_INFO_PROFILE = "profile"
117
+ MODEL_INFO_PROFILE_DEFAULT = False
118
+ MODEL_INFO_NUM_PARAMS = "num_params"
119
+ MODEL_INFO_NUM_PARAMS_DEFAULT = None
120
+ MODEL_INFO_HIDDEN_SIZE = "hidden_size"
121
+ MODEL_INFO_HIDDEN_SIZE_DEFAULT = None
122
+ MODEL_INFO_NUM_LAYERS = "num_layers"
123
+ MODEL_INFO_NUM_LAYERS_DEFAULT = None
124
+
125
+ MODEL_INFO_KEY_DEFAULT_DICT = {
126
+ MODEL_INFO_PROFILE: MODEL_INFO_PROFILE_DEFAULT,
127
+ MODEL_INFO_NUM_PARAMS: MODEL_INFO_NUM_PARAMS_DEFAULT,
128
+ MODEL_INFO_HIDDEN_SIZE: MODEL_INFO_HIDDEN_SIZE_DEFAULT,
129
+ MODEL_INFO_NUM_LAYERS: MODEL_INFO_NUM_LAYERS_DEFAULT
130
+ }
131
+
132
+ #########################################
133
+ # autotuner search space constants
134
+ #########################################
135
+
136
+ DEFAULT_HF_CONFIG = {
137
+ "train_batch_size": "auto",
138
+ "train_micro_batch_size_per_gpu": "auto",
139
+ "gradient_accumulation_steps": "auto",
140
+ }
141
+
142
+ DEFAULT_MIN_MEM_CONFIG = {
143
+ "train_micro_batch_size_per_gpu": 1,
144
+ "zero_optimization": {
145
+ "stage": 3
146
+ },
147
+ "memory_break_down": False
148
+ }
149
+
150
+ DEFAULT_TUNING_SPACE_ZERO_0 = {"zero_optimization": {"stage": 0}}
151
+
152
+ DEFAULT_TUNING_SPACE_ZERO_1 = {
153
+ "zero_optimization": {
154
+ "stage": 1,
155
+ "reduce_bucket_size": [5e7, 5e8, 1e9],
156
+ "allgather_bucket_size": [5e7, 5e8, 1e9],
157
+ }
158
+ }
159
+
160
+ DEFAULT_TUNING_SPACE_ZERO_2 = {
161
+ "zero_optimization": {
162
+ "stage": 2,
163
+ "overlap_comm": [True, False],
164
+ "reduce_scatter": [False, True],
165
+ "reduce_bucket_size": [5e7, 5e8, 1e9],
166
+ "allgather_bucket_size": [5e7, 5e8, 1e9],
167
+ "contiguous_gradients": [False, True]
168
+ },
169
+ }
170
+
171
+ DEFAULT_TUNING_SPACE_ZERO_3 = {
172
+ "zero_optimization": {
173
+ "stage": 3,
174
+ "overlap_comm": [True, False],
175
+ "reduce_scatter": [False, True],
176
+ "reduce_bucket_size": [5e7, 5e8, 1e9],
177
+ "allgather_partitions": [True, False],
178
+ "allgather_bucket_size": [5e7, 5e8, 1e9],
179
+ "contiguous_gradients": [False, True]
180
+ },
181
+ }
182
+
183
+ GLOBAL_TUNING_SPACE = 'global'
184
+ # TUNING_MICRO_BATCH_SIZE_PREFIX="tune_micro_batch_size_z"
185
+ TUNING_MICRO_BATCH_SIZE_PREFIX = "z"
lib/python3.12/site-packages/deepspeed/autotuning/scheduler.py ADDED
@@ -0,0 +1,432 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ import copy
7
+
8
+ import json
9
+ import subprocess
10
+ import sys
11
+ import threading
12
+ import time
13
+ import base64
14
+
15
+ import os
16
+ import hjson
17
+ from tqdm import tqdm
18
+
19
+ from ..utils import logger
20
+ from .constants import AUTOTUNING, AUTOTUNING_METRIC_PATH, BUFSIZE
21
+ from .utils import get_val_by_key, search_error, was_interruptted
22
+ """
23
+ thread-0: loop over experiment queue dispatching experiments if they become available
24
+ thread-N: start each experiment in its own thread
25
+ """
26
+
27
+ from deepspeed import comm as dist
28
+
29
+ TIMEOUT = 5
30
+
31
+
32
+ class ResourceManager:
33
+
34
+ def __init__(self, args, hosts, num_gpus_per_node, results_dir, exps_dir, arg_mappings):
35
+ self.results_dir = results_dir
36
+ self.exps_dir = exps_dir
37
+
38
+ self.nodes = []
39
+ self.num_gpus_per_node = num_gpus_per_node
40
+ for host in hosts:
41
+ self.nodes.append(Node(host, num_gpus_per_node))
42
+
43
+ self.experiment_queue = []
44
+ self.running_experiments = {}
45
+ self.finished_experiments = {}
46
+ self.experiment_count = 0
47
+ self.exp_paths = set()
48
+ self.args = args
49
+
50
+ self.arg_mappings = {}
51
+ if arg_mappings is not None:
52
+ for k, v in arg_mappings.items():
53
+ k = k.strip()
54
+ v = v.strip()
55
+ if k not in self.arg_mappings:
56
+ self.arg_mappings[k] = v
57
+
58
+ def schedule_experiments(self, exp_paths):
59
+ for exp_path in exp_paths:
60
+ if exp_path in self.exp_paths:
61
+ continue
62
+ else:
63
+ self.exp_paths.add(exp_path)
64
+ with open(exp_path, "r") as fd:
65
+ exp = hjson.load(fd)
66
+ exp["exp_id"] = self.experiment_count
67
+ self.experiment_count += 1
68
+
69
+ result_dir = exp["result_dir"] = os.path.join(self.results_dir, exp['name'])
70
+ if AUTOTUNING in exp["ds_config"]:
71
+ metric_file = os.path.join(result_dir, "metrics.json")
72
+ exp["ds_config"][AUTOTUNING][AUTOTUNING_METRIC_PATH] = metric_file
73
+ stderr_file = os.path.join(result_dir, "stderr.log")
74
+ model_info_file = os.path.join(result_dir, "model_info.json")
75
+ metric_file = os.path.join(result_dir, "metrics.json")
76
+
77
+ # skip existing experiments (except for the ones that were interrupted)
78
+ if os.path.exists(result_dir) and os.path.exists(stderr_file):
79
+ if not was_interruptted(stderr_file):
80
+ err = search_error(stderr_file)
81
+ exp_id = exp["exp_id"]
82
+ self.finished_experiments[exp_id] = (exp, err)
83
+ if err or os.path.exists(metric_file) or os.path.exists(model_info_file):
84
+ logger.info(f"Skipping exp {exp['name']} whose result already exists")
85
+ continue
86
+
87
+ self.experiment_queue.append(exp)
88
+
89
+ def run_job(self, exp: dict, reservations):
90
+ exp_id = exp["exp_id"]
91
+ exp["master_port"] = self.args.master_port + exp_id
92
+ exp["result_dir"] = os.path.join(self.results_dir, exp['name'])
93
+ user_script = self.args.user_script
94
+ user_args = self.args.user_args
95
+
96
+ # overwrite the user arg in the arg_mappings
97
+ for key, val in self.arg_mappings.items():
98
+ nval = get_val_by_key(exp, key)
99
+ if nval and str(nval) != "auto":
100
+ if val in user_args:
101
+ idx = user_args.index(val)
102
+ user_args[idx + 1] = str(nval)
103
+ else:
104
+ user_args.append(val)
105
+ user_args.append(str(nval))
106
+
107
+ t = threading.Thread(target=run_experiment, args=(exp, reservations, user_script, user_args))
108
+ t.start()
109
+ self.running_experiments[exp_id] = (t, exp, reservations, time.time())
110
+
111
+ def experiment_check(self, pbar):
112
+ finished_exps = []
113
+ for exp_id, exp_data in self.running_experiments.items():
114
+ thread, exp_json, reservations, start_time = exp_data
115
+ logger.debug(f"Checking exp_id = {exp_id}, alive = {thread.is_alive()}")
116
+ thread.join(timeout=TIMEOUT)
117
+ if not thread.is_alive():
118
+ exp_dir = exp_json["result_dir"]
119
+ stderr_file = os.path.join(exp_dir, "stderr.log")
120
+ err = search_error(stderr_file)
121
+ finished_exps.append((exp_id, reservations))
122
+ self.finished_experiments[exp_id] = (exp_json, err)
123
+ duration = time.time() - start_time
124
+ logger.debug(f"Finished exp_id = {exp_id}, duration={duration:.2f} sec")
125
+ pbar.update(len(finished_exps))
126
+ for exp_id, reservations in finished_exps:
127
+ for reservation in reservations:
128
+ reservation.restore_slots()
129
+ self.running_experiments.pop(exp_id)
130
+ time.sleep(TIMEOUT)
131
+
132
+ def resource_request(self, exp):
133
+ num_gpus, num_nodes = exp['num_gpus'], exp['num_nodes']
134
+ slot_request = num_gpus
135
+ reservations = []
136
+ for node in self.nodes:
137
+ if num_nodes == 0:
138
+ break
139
+ slots = node.reserve_slots(slot_request=slot_request)
140
+ if slots:
141
+ reservations.append(Reservation(node=node, slots=slots))
142
+ num_nodes -= 1
143
+
144
+ if num_nodes == 0:
145
+ # request satisfied
146
+ return reservations
147
+ else:
148
+ # request not satisfied
149
+ for reservation in reservations:
150
+ reservation.restore_slots()
151
+
152
+ def status(self):
153
+ status = ""
154
+ for node in self.nodes:
155
+ status += f"{node.host} ({len(node.idle_slots)} idle gpus), "
156
+ return status[:-1]
157
+
158
+ def run(self):
159
+ pbar = tqdm(total=len(self.experiment_queue))
160
+
161
+ while len(self.experiment_queue) > 0:
162
+ exp = self.experiment_queue.pop(0)
163
+ logger.debug(f'Popped exp_id = {exp["exp_id"]} from the queue')
164
+ logger.debug(f'Resource status: {self.status()}')
165
+ reservations = self.resource_request(exp)
166
+
167
+ if not reservations:
168
+ logger.debug(f'Unable to schedule exp_id = {exp["exp_id"]}')
169
+ self.experiment_queue.insert(0, exp)
170
+ logger.debug(f'Put exp_id = {exp["exp_id"]} back into the queue')
171
+ self.experiment_check(pbar)
172
+ else:
173
+ desc = ""
174
+ for reservation in reservations:
175
+ reservation.slots.sort()
176
+ slots = ",".join(map(str, reservation.slots))
177
+ desc += f"{reservation.node.host}:{slots}@"
178
+ desc = desc[:-1]
179
+ logger.debug(f'Running exp_id = {exp["exp_id"]} on {desc}')
180
+ self.run_job(exp, reservations)
181
+
182
+ # All pending experiments are scheduled, waiting for them to complete
183
+ while len(self.running_experiments) > 0:
184
+ self.experiment_check(pbar)
185
+
186
+ def save_exp_results_to_database(self, message, ranks=None, path=None):
187
+ """Print message when one of following condition meets
188
+
189
+ + not dist.is_initialized()
190
+ + dist.get_rank() in ranks if ranks is not None or ranks = [-1]
191
+
192
+ Args:
193
+ message (str)
194
+ ranks (list)
195
+ path (str)
196
+
197
+ """
198
+ should_log = not dist.is_initialized()
199
+ ranks = ranks or []
200
+ my_rank = dist.get_rank() if dist.is_initialized() else -1
201
+ if ranks and not should_log:
202
+ should_log = ranks[0] == -1
203
+ should_log = should_log or (my_rank in set(ranks))
204
+ logger.debug(f"*** Should log: {should_log}")
205
+ if should_log:
206
+ message['rank'] = my_rank
207
+ with open(path, 'a') as outfile:
208
+ json.dump(message, outfile)
209
+ outfile.write('\n')
210
+
211
+ def parse_results(self, metric):
212
+ """ Parses the metric file of the finished experiments to select the optimal DeepSpeed configuration.
213
+
214
+ Args:
215
+ finished_experiments (dcit): a dictionary of experiment id and experiment description.
216
+
217
+ Returns:
218
+ The path to the result folder of the experiment with the optimal configuration.
219
+ """
220
+ max_throughput = sys.float_info.min
221
+ best_exp_id = -1
222
+ for exp_id, (exp, err) in self.finished_experiments.items():
223
+ if err:
224
+ logger.info(
225
+ f"The experiment exp_id = {exp_id}, exp_name = {exp['name']}, did not run successfully with error = {err}, thus a metrics.txt does not exist for it. Check the stderr.log in {exp['result_dir']}"
226
+ )
227
+ continue
228
+
229
+ metric_file = exp["ds_config"][AUTOTUNING][AUTOTUNING_METRIC_PATH]
230
+
231
+ if os.path.exists(metric_file):
232
+ with open(metric_file, 'r') as f:
233
+ results = hjson.load(f)
234
+ curr_throughput = results[metric]
235
+ if curr_throughput > max_throughput:
236
+ max_throughput = curr_throughput
237
+ best_exp_id = exp_id
238
+ exp['results'] = results
239
+
240
+ if best_exp_id != -1:
241
+ best_exp, _ = self.finished_experiments[best_exp_id]
242
+ return best_exp, max_throughput
243
+
244
+ return exp, None
245
+
246
+ def clear(self):
247
+ """Clear experiment queues, does not reset self.experiment_count
248
+ """
249
+ self.experiment_queue = []
250
+ # clean up the running experiments
251
+ for exp_id, exp_data in self.running_experiments.items():
252
+ thread, exp_json, reservations, start_time = exp_data
253
+ clean_up(exp_json, reservations)
254
+ self.running_experiments = {}
255
+ self.finished_experiments = {}
256
+ self.exp_paths = set()
257
+
258
+
259
+ class Node:
260
+
261
+ def __init__(self, host, max_slots):
262
+ self.host = host
263
+ self.max_slots = max_slots
264
+ self.idle_slots = list(range(max_slots))
265
+
266
+ def reserve_slots(self, slot_request: int) -> list:
267
+ if len(self.idle_slots) >= slot_request:
268
+ return [self.idle_slots.pop(0) for _ in range(slot_request)]
269
+
270
+ def restore_slots(self, slots: list):
271
+ self.idle_slots += slots
272
+
273
+
274
+ class Reservation:
275
+
276
+ def __init__(self, node, slots):
277
+ self.node = node
278
+ self.slots = slots
279
+
280
+ def restore_slots(self):
281
+ self.node.restore_slots(self.slots)
282
+
283
+ def desc(self):
284
+ slots = ",".join(map(str, self.slots))
285
+ return f"{self.node.host}:{slots}@"
286
+
287
+
288
+ def get_job_id():
289
+ # Infrastructure-specific job-id
290
+ infra_job_id = None
291
+ if "DLWS_JOB_ID" in os.environ:
292
+ infra_job_id = os.environ["DLWS_JOB_ID"]
293
+ elif "DLTS_JOB_ID" in os.environ:
294
+ infra_job_id = os.environ["DLTS_JOB_ID"]
295
+ else:
296
+ infra_job_id = "unknown-job-id"
297
+
298
+ return infra_job_id
299
+
300
+
301
+ def get_user():
302
+ user = None
303
+ if "USER" in os.environ:
304
+ user = os.environ["USER"]
305
+ else:
306
+ user = "unknown-user"
307
+ return user
308
+
309
+
310
+ def run_experiment(exp: dict, reservations, user_script, user_args):
311
+ include_str = ""
312
+ for reservation in reservations:
313
+ reservation.slots.sort()
314
+ slots = ",".join(map(str, reservation.slots))
315
+ include_str += f"{reservation.node.host}:{slots}@"
316
+ include_str = include_str[:-1]
317
+ master_port = exp["master_port"]
318
+ hostfile = exp["hostfile"]
319
+ exp["launcher_args"] = [
320
+ "--hostfile",
321
+ f"{hostfile}",
322
+ "--include",
323
+ f"{include_str}",
324
+ "--master_port",
325
+ str(master_port),
326
+ ]
327
+ logger.debug(f'launcher args={exp["launcher_args"]}')
328
+
329
+ exp["user"] = get_user()
330
+ exp["job_id"] = get_job_id()
331
+ exp_dir = exp["result_dir"]
332
+ os.makedirs(exp_dir, exist_ok=True)
333
+ ds_config_path = os.path.join(exp_dir, "ds_config.json")
334
+ exp["ds_config_path"] = ds_config_path
335
+
336
+ ds_config = copy.deepcopy(exp["ds_config"])
337
+ ds_config_json = json.dumps(ds_config).encode('utf-8')
338
+
339
+ exp["ds_config_base64"] = base64.urlsafe_b64encode(ds_config_json).decode('utf-8')
340
+
341
+ with open(exp["ds_config_path"], "w", buffering=BUFSIZE) as fd:
342
+ json.dump(ds_config, fd)
343
+ fd.flush()
344
+ os.fsync(fd)
345
+ path = exp["ds_config_path"]
346
+ logger.info(f"Scheduler wrote ds_config to {path}, {os.path.abspath(path)}")
347
+
348
+ with open(os.path.join(exp_dir, "exp.json"), "w", buffering=BUFSIZE) as fd:
349
+ json.dump(exp, fd)
350
+ fd.flush()
351
+ os.fsync(fd)
352
+ path = os.path.join(exp_dir, "exp.json")
353
+ logger.info(f"Scheduler wrote exp to {path}, {os.path.abspath(path)}")
354
+
355
+ # remove "--deepspeed_config ds_config.json" from user_args
356
+ if user_args:
357
+ if "--deepspeed_config" in user_args:
358
+ idx = user_args.index("--deepspeed_config")
359
+ # "--deepspeed_config" is omitted in HF
360
+ elif "--deepspeed" in user_args:
361
+ idx = user_args.index("--deepspeed")
362
+ assert idx < len(user_args), "there is no ds_config file specified after --deepspeed_config or --deepspeed"
363
+ # user_args[idx + 1] = exp["ds_config_path"]
364
+ # pass base64 serialized ds_config to launcher
365
+ user_args[idx + 1] = exp["ds_config_base64"]
366
+
367
+ exp["user_script"] = user_script
368
+ exp["user_args"] = user_args
369
+
370
+ cmd = ["deepspeed"] + exp["launcher_args"] + [user_script] + user_args
371
+
372
+ assert len(exp["launcher_args"]) > 0, "must provide launcher args"
373
+
374
+ with open(os.path.join(exp_dir, "cmd.txt"), "w", buffering=BUFSIZE) as fd:
375
+ fd.write(" ".join(cmd))
376
+ fd.write("\n")
377
+ fd.flush()
378
+ os.fsync(fd)
379
+
380
+ logger.info(
381
+ f"Launching exp_id = {exp['exp_id']}, exp_name = {exp['name']}, with resource = {include_str}, and ds_config = {os.path.abspath(ds_config_path)}"
382
+ )
383
+
384
+ with open(os.path.join(exp_dir, "stdout.log"), "wb") as out, open(os.path.join(exp_dir, "stderr.log"),
385
+ "wb") as err:
386
+ result = subprocess.Popen(cmd, stdout=out, stderr=err)
387
+ result.wait()
388
+ out.flush()
389
+ err.flush()
390
+ os.fsync(out)
391
+ os.fsync(err)
392
+
393
+ clean_up(exp, reservations)
394
+
395
+ logger.info(f"Done running exp_id = {exp['exp_id']}, exp_name = {exp['name']}, with resource = {include_str}")
396
+
397
+
398
+ PDSH_MAX_FAN_OUT = 1024
399
+
400
+
401
+ def clean_up(exp: dict, reservations):
402
+ env = os.environ.copy()
403
+ env['PDSH_RCMD_TYPE'] = 'ssh'
404
+
405
+ nodes_str = ""
406
+ for reservation in reservations:
407
+ nodes_str += f"{reservation.node.host},"
408
+ nodes_str = nodes_str[:-1]
409
+ logger.debug(f"Cleaning up exp_id = {exp['exp_id']} on the following workers: {nodes_str}")
410
+
411
+ # PDSH flags for max node fan out and specific hosts to launch on
412
+ # See https://linux.die.net/man/1/pdsh for flag details
413
+ pdsh_cmd = ['pdsh', '-f', str(PDSH_MAX_FAN_OUT), '-w', nodes_str]
414
+
415
+ kill_cmd = [
416
+ 'pkill',
417
+ '-f',
418
+ exp['name'],
419
+ ]
420
+ cmd = pdsh_cmd + kill_cmd
421
+ logger.debug("cmd = {}".format(' '.join(cmd)))
422
+
423
+ result = subprocess.Popen(cmd, env=env)
424
+ result.wait()
425
+
426
+ # In case of failure must propagate the error-condition back to the caller (usually shell). The
427
+ # actual error and traceback should have been printed in the subprocess, so in order to avoid
428
+ # unnecessary noise we just quietly exit here with the same code as the subprocess
429
+ if result.returncode > 0:
430
+ sys.exit(result.returncode)
431
+
432
+ logger.info(f"Done cleaning up exp_id = {exp['exp_id']} on the following workers: {nodes_str}")
lib/python3.12/site-packages/deepspeed/autotuning/tuner/__init__.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ from .index_based_tuner import RandomTuner, GridSearchTuner
7
+ # from .ga_tuner import GATuner
8
+ from .model_based_tuner import ModelBasedTuner
lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (347 Bytes). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/base_tuner.cpython-312.pyc ADDED
Binary file (3.5 kB). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/cost_model.cpython-312.pyc ADDED
Binary file (2.54 kB). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/index_based_tuner.cpython-312.pyc ADDED
Binary file (2.18 kB). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/model_based_tuner.cpython-312.pyc ADDED
Binary file (8.2 kB). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/utils.cpython-312.pyc ADDED
Binary file (3.94 kB). View file
 
lib/python3.12/site-packages/deepspeed/autotuning/tuner/base_tuner.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ import sys
7
+
8
+ from deepspeed.autotuning.constants import *
9
+ from deepspeed.autotuning.utils import write_experiments
10
+ from deepspeed.utils import logger
11
+
12
+
13
+ class BaseTuner:
14
+
15
+ def __init__(self, exps, resource_manager, metric):
16
+ self.all_exps = exps
17
+ self.rm = resource_manager
18
+ self.best_iter = 0
19
+ self.best_exp = None
20
+ self.best_metric_val = None
21
+ self.metric = metric if metric else AUTOTUNING_METRIC_DEFAULT
22
+ logger.info(f"total number of exps = {len(self.all_exps)}")
23
+
24
+ def has_next(self):
25
+ """Whether there exists more configurations for evaluation"""
26
+ if len(self.all_exps) > 0:
27
+ return True
28
+ else:
29
+ return False
30
+
31
+ def next_batch(self, sample_size):
32
+ """Select the next batch of configurations for evaluation"""
33
+ raise NotImplementedError
34
+
35
+ def update(self):
36
+ """"Update the tuner with what configurations have been evaluated and their performance results"""
37
+
38
+ def tune(self, sample_size=1, n_trials=1000, early_stopping=None):
39
+ i = 0
40
+ try:
41
+ while i < n_trials and self.has_next():
42
+ # Select the next batch of configuration for evaluation
43
+ sampled_exps = self.next_batch(sample_size)
44
+ # Generate experiments for measurement of performance
45
+ exp_paths = write_experiments(sampled_exps, self.rm.exps_dir)
46
+ self.rm.schedule_experiments(exp_paths)
47
+ self.rm.run()
48
+ exp, metric_val = self.rm.parse_results(self.metric)
49
+ if self.best_exp is None or self.best_metric_val is None or (metric_val
50
+ and metric_val > self.best_metric_val):
51
+ # logger.info(f"tuner finds better = {exp}")
52
+ self.best_exp = exp
53
+ self.best_metric_val = metric_val
54
+ self.best_iter = i
55
+
56
+ i += len(sampled_exps)
57
+
58
+ # Update the tuner with evaluated performance results
59
+ self.update()
60
+
61
+ self.rm.clear()
62
+
63
+ # Early stop if no more promising configurations are likely to be found
64
+ if early_stopping and i >= self.best_iter + early_stopping:
65
+ logger.info(
66
+ f"Tuner early stopped at iteration {i}. Best iteration is {self.best_iter}. Early stopping threshold is {early_stopping}"
67
+ )
68
+ break
69
+ return i
70
+ except:
71
+ logger.info("Tuner Error:", sys.exc_info()[0])
72
+ return i
lib/python3.12/site-packages/deepspeed/autotuning/tuner/cost_model.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ from .utils import *
7
+
8
+ try:
9
+ import xgboost as xgb
10
+ except ImportError:
11
+ xgb = None
12
+
13
+
14
+ class XGBoostCostModel():
15
+
16
+ def __init__(self, loss_type, num_threads=None, log_interval=25, upper_model=None):
17
+
18
+ assert xgb is not None, "missing requirements, please install deepspeed w. 'autotuning_ml' extra."
19
+
20
+ self.loss_type = loss_type
21
+
22
+ if loss_type == "reg":
23
+ self.xgb_params = {
24
+ "max_depth": 3,
25
+ "gamma": 0.0001,
26
+ "min_child_weight": 1,
27
+ "subsample": 1.0,
28
+ "eta": 0.3,
29
+ "lambda": 1.0,
30
+ "alpha": 0,
31
+ "objective": "reg:linear",
32
+ }
33
+ elif loss_type == "rank":
34
+ self.xgb_params = {
35
+ "max_depth": 3,
36
+ "gamma": 0.0001,
37
+ "min_child_weight": 1,
38
+ "subsample": 1.0,
39
+ "eta": 0.3,
40
+ "lambda": 1.0,
41
+ "alpha": 0,
42
+ "objective": "rank:pairwise",
43
+ }
44
+ else:
45
+ raise RuntimeError("Invalid loss type: " + loss_type)
46
+
47
+ self.xgb_params["verbosity"] = 0
48
+ if num_threads:
49
+ self.xgb_params["nthread"] = num_threads
50
+
51
+ def fit(self, xs, ys):
52
+ x_train = np.array(xs, dtype=np.float32)
53
+ y_train = np.array(ys, dtype=np.float32)
54
+ y_max = np.max(y_train)
55
+ y_train = y_train / max(y_max, 1e-9)
56
+
57
+ index = np.random.permutation(len(x_train))
58
+ dtrain = xgb.DMatrix(x_train[index], y_train[index])
59
+
60
+ self.bst = xgb.train(self.xgb_params, dtrain)
61
+
62
+ def predict(self, xs):
63
+
64
+ features = xgb.DMatrix(xs)
65
+
66
+ return self.bst.predict(features)
lib/python3.12/site-packages/deepspeed/autotuning/tuner/index_based_tuner.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ import random
7
+
8
+ from .base_tuner import BaseTuner
9
+
10
+
11
+ class RandomTuner(BaseTuner):
12
+ """Explore the search space in random order"""
13
+
14
+ def __init__(self, exps: list, resource_manager, metric):
15
+ super().__init__(exps, resource_manager, metric)
16
+
17
+ def next_batch(self, sample_size=1):
18
+ if sample_size > len(self.all_exps):
19
+ sample_size = len(self.all_exps)
20
+
21
+ sampled_batch = random.sample(self.all_exps, sample_size)
22
+ self.all_exps = [x for x in self.all_exps if x not in sampled_batch]
23
+
24
+ return sampled_batch
25
+
26
+
27
+ class GridSearchTuner(BaseTuner):
28
+ """Explore the search space in sequential order"""
29
+
30
+ def __init__(self, exps: list, resource_manager, metric):
31
+ super().__init__(exps, resource_manager, metric)
32
+
33
+ def next_batch(self, sample_size=1):
34
+ if sample_size > len(self.all_exps):
35
+ sample_size = len(self.all_exps)
36
+
37
+ sampled_batch = self.all_exps[0:sample_size]
38
+ self.all_exps = [x for x in self.all_exps if x not in sampled_batch]
39
+
40
+ return sampled_batch
lib/python3.12/site-packages/deepspeed/autotuning/tuner/model_based_tuner.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ import hjson
7
+
8
+ from ..constants import AUTOTUNING, AUTOTUNING_METRIC_PATH
9
+ from .base_tuner import BaseTuner
10
+ from .cost_model import XGBoostCostModel
11
+ from .utils import *
12
+ from ..utils import *
13
+ import numbers
14
+ from ..constants import AUTOTUNING_METRIC_LATENCY
15
+
16
+ INIT_NUM = 2
17
+
18
+
19
+ class ModelBasedTuner(BaseTuner):
20
+ """Exploring the search space with a cost model"""
21
+
22
+ def __init__(self, exps: list, resource_manager, metric, tuning_space):
23
+ super().__init__(exps, resource_manager, metric)
24
+ self.tuning_space = tuning_space
25
+ self.best_iter = 0
26
+
27
+ self.all_configs = [e['ds_config'] for e in exps]
28
+ self.num_all_configs = len(self.all_configs)
29
+
30
+ self.dims = dict_to_dims(self.tuning_space)
31
+
32
+ logger.info(f"Create config dim: {self.dims}, all configs: {self.num_all_configs}")
33
+
34
+ self.visited = set([])
35
+
36
+ self.trials = []
37
+ self.trial_pt = 0
38
+
39
+ init_num = min(INIT_NUM, self.num_all_configs)
40
+
41
+ for _ in range(init_num):
42
+ exp_feature = np.random.randint(self.num_all_configs)
43
+ exp_feature = 0
44
+ while exp_feature in self.visited:
45
+ exp_feature = np.random.randint(self.num_all_configs)
46
+ self.trials.append(exp_feature)
47
+ self.visited.add(exp_feature)
48
+
49
+ self.cost_model = XGBoostCostModel("rank")
50
+
51
+ self.evaluated_configs = []
52
+ self.evaluated_perf = []
53
+
54
+ self.train_ct = 0
55
+
56
+ self.random_exploration_ratio = 0.2 # do random exploration
57
+
58
+ def find_estimated_top_configs(self):
59
+ """Use the cost model to predict the estimated performance of configurations and find the top ones for the next round of evaluation"""
60
+
61
+ configs = []
62
+
63
+ for c in self.all_configs:
64
+ flattened_ds_config = flatten(c)
65
+ feature_val = []
66
+ for k, v in flattened_ds_config.items():
67
+ if isinstance(v, numbers.Number):
68
+ feature_val.append(v)
69
+ configs.append(feature_val)
70
+ # print(configs)
71
+ # TODO the current implementation requires that all configs have the same shape.
72
+ configs = np.array(configs, dtype=np.float32)
73
+ estimates = self.cost_model.predict(configs)
74
+
75
+ n = len(estimates)
76
+ top_idx = np.argsort(estimates)
77
+ top_idx_ret = top_idx if self.metric == AUTOTUNING_METRIC_LATENCY else top_idx[::-1][:n]
78
+
79
+ # top_configs = [self.all_configs[i] for i in top_idx]
80
+
81
+ return top_idx_ret
82
+
83
+ def next_batch(self, sample_size):
84
+ sampled_batch = []
85
+
86
+ counter = 0
87
+ while counter < sample_size:
88
+
89
+ if len(self.visited) >= self.num_all_configs:
90
+ break
91
+
92
+ while self.trial_pt < len(self.trials):
93
+ logger.debug(f"trials: {self.trials}")
94
+ # Select top promising trials
95
+ index = self.trials[self.trial_pt]
96
+ if index not in self.visited:
97
+ break
98
+ self.trial_pt += 1
99
+
100
+ # To avoid over-exploitation, randomly select one that has not been explored.
101
+ rand = np.random.rand()
102
+ if rand < self.random_exploration_ratio:
103
+ # Do normal selection
104
+ feature = np.random.choice(self.trials)
105
+ while index in self.visited:
106
+ index = np.random.randint(self.num_all_configs)
107
+
108
+ # Need to track both the sampled configs and indices
109
+
110
+ sampled_batch.append(self.all_exps[index])
111
+ self.visited.add(index)
112
+ counter += 1
113
+
114
+ return sampled_batch
115
+
116
+ def has_next(self):
117
+ return len(self.visited) < self.num_all_configs
118
+
119
+ def update(self):
120
+ for exp_id, (exp, err) in self.rm.finished_experiments.items():
121
+ feature_val = []
122
+ if err:
123
+ logger.info(
124
+ f"Skipping exp_id = {exp_id}, exp_name = {exp['name']}, the experiment did not run successfully with error = {err}, thus a metrics.txt does not exist for it. Please check the stderr.log in {exp['result_dir']}"
125
+ )
126
+ ds_config = exp["ds_config"]
127
+ flattened_ds_config = flatten(ds_config)
128
+ for k, v in flattened_ds_config.items():
129
+ if isinstance(v, numbers.Number):
130
+ feature_val.append(v)
131
+ self.evaluated_configs.append(feature_val)
132
+ self.evaluated_perf.append(0.0)
133
+ continue
134
+
135
+ p = exp["ds_config"][AUTOTUNING][AUTOTUNING_METRIC_PATH]
136
+ with open(p, 'r') as f:
137
+ results = hjson.load(f)
138
+ curr_iter = results[self.metric]
139
+ logger.debug(f"parsing the results for {exp_id}, Result is {curr_iter}")
140
+
141
+ ds_config = exp["ds_config"]
142
+ flattened_ds_config = flatten(ds_config)
143
+ for k, v in flattened_ds_config.items():
144
+ if isinstance(v, numbers.Number):
145
+ feature_val.append(v)
146
+ self.evaluated_configs.append(feature_val)
147
+ self.evaluated_perf.append(curr_iter)
148
+
149
+ logger.debug(f"**Evaluated configs: {len(self.evaluated_configs)}, evaluated perf: {self.evaluated_perf}")
150
+
151
+ self.cost_model.fit(self.evaluated_configs, self.evaluated_perf)
152
+
153
+ estimated_top_configs = self.find_estimated_top_configs()
154
+
155
+ self.trials = estimated_top_configs
156
+ self.trial_pt = 0
157
+ self.train_ct += 1
lib/python3.12/site-packages/deepspeed/autotuning/tuner/utils.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ import numpy as np
7
+ import itertools
8
+ from ..utils import *
9
+ import collections.abc
10
+
11
+
12
+ def index_to_feature(p, dims):
13
+ """convert index form (single integer) to feature form (vector)"""
14
+ feature = []
15
+ for dim in dims:
16
+ feature.append(p % dim)
17
+ p //= dim
18
+ return feature
19
+
20
+
21
+ def feature_to_index(feature, dims):
22
+ """convert feature form (vector) to index form (single integer)"""
23
+ p = 0
24
+ for j, k in enumerate(feature):
25
+ print("j:", "k:", k, "dims", dims[:j])
26
+ p += int(np.prod(dims[:j])) * k
27
+ return p
28
+
29
+
30
+ def dict_to_dims(tuning_space):
31
+
32
+ dims = []
33
+
34
+ for key, val in tuning_space.items():
35
+ if isinstance(val, dict):
36
+ dims.extend(dict_to_dims(val))
37
+ elif isinstance(val, list):
38
+ dims.append(len(val))
39
+ else:
40
+ dims.append(1)
41
+
42
+ return dims
43
+
44
+
45
+ def gen_combinations(d: dict):
46
+ keys, values = d.keys(), d.values()
47
+ for v in values:
48
+ if not isinstance(v, list):
49
+ v = [v]
50
+ values_choices = (gen_combinations(v) if isinstance(v, dict) else get_list(v) for v in values)
51
+ for comb in itertools.product(*values_choices):
52
+ yield dict(zip(keys, comb))
53
+
54
+
55
+ def flatten(d, parent_key='', sep='_'):
56
+ items = []
57
+ for k, v in d.items():
58
+ new_key = parent_key + sep + k if parent_key else k
59
+ if isinstance(v, collections.abc.MutableMapping):
60
+ items.extend(flatten(v, new_key, sep=sep).items())
61
+ else:
62
+ items.append((new_key, v))
63
+ return dict(items)
64
+
65
+
66
+ def dict_to_feature(feature_dict, keys, max_value=None):
67
+ """Extract values from dict"""
68
+ feature = []
69
+ for key, val in feature_dict.items(): # First level
70
+ if key not in keys:
71
+ continue
72
+ if val is None or val == "auto" or key == "autotuning" or val == "":
73
+ continue
74
+ if isinstance(val, dict):
75
+ feature.append(dict_to_feature(val, max_value))
76
+ else:
77
+ feature.append(float(val))
78
+
79
+ # normalization, should not matter in tree models
80
+ if max_value is not None:
81
+ norm_feature = []
82
+ for f, mv in zip(feature, max_value):
83
+ norm_feature.append(f / mv)
84
+ feature = norm_feature
85
+
86
+ return feature
lib/python3.12/site-packages/deepspeed/autotuning/utils.py ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ import re
7
+ import collections.abc
8
+ import os
9
+ import json
10
+ from deepspeed.runtime.constants import GRADIENT_ACCUMULATION_STEPS, TRAIN_MICRO_BATCH_SIZE_PER_GPU
11
+ import itertools
12
+ import copy
13
+
14
+ from ..utils import logger
15
+
16
+
17
+ def search_error(filename):
18
+ if not os.path.exists(filename):
19
+ return "stderr.log does not exist"
20
+ with open(filename) as f:
21
+ for line in f:
22
+ for s in ["Error", "error", "ERROR"]:
23
+ idx = line.find(s)
24
+ if idx != -1:
25
+ return line[idx + len(s):].lstrip(": ")
26
+ return None
27
+
28
+
29
+ def was_interruptted(filename):
30
+ if not os.path.exists(filename):
31
+ return "stderr.log does not exist"
32
+ with open(filename) as f:
33
+ for line in f:
34
+ s = "KeyboardInterrupt"
35
+ idx = line.find(s)
36
+ if idx != -1:
37
+ return True
38
+ return False
39
+
40
+
41
+ def find_replace_str(value, replace_dict):
42
+ if not isinstance(value, str):
43
+ return str(value)
44
+
45
+ matches = re.findall(r"\$[\w]+", value)
46
+ for var in matches:
47
+ var_key = var.replace("$", "").lower()
48
+ if var_key == "nvme_path":
49
+ continue
50
+ assert var_key in replace_dict, f"unknown var key: {var_key}, in {replace_dict}"
51
+ if isinstance(replace_dict[var_key], str):
52
+ value = value.replace(var, replace_dict[var_key])
53
+ else:
54
+ assert len(matches) == 1, "unable to replace multiple non-string matches"
55
+ value = replace_dict[var_key]
56
+ return value
57
+
58
+
59
+ def find_replace(target, replace_dict):
60
+ if isinstance(target, dict):
61
+ for key, value in target.items():
62
+ if isinstance(value, str):
63
+ target[key] = find_replace_str(value, replace_dict)
64
+ if isinstance(value, list):
65
+ for i in range(len(value)):
66
+ value[i] = find_replace_str(value[i], replace_dict)
67
+ if isinstance(value, dict):
68
+ find_replace(value, replace_dict)
69
+ elif isinstance(target, list):
70
+ for i in range(len(target)):
71
+ target[i] = str(find_replace_str(target[i], replace_dict))
72
+
73
+
74
+ def get_list(val):
75
+ if not isinstance(val, list):
76
+ return [val]
77
+ else:
78
+ return val
79
+
80
+
81
+ def combine_dict(d, u):
82
+ for k, v in u.items():
83
+ if isinstance(v, collections.abc.Mapping):
84
+ d[k] = combine_dict(d.get(k, {}), v)
85
+ else:
86
+ if k not in d:
87
+ d[k] = v
88
+ else:
89
+ if not isinstance(d[k], list):
90
+ d[k] = [d[k]]
91
+ d[k].extend(i for i in get_list(v) if i not in d[k])
92
+ return d
93
+
94
+
95
+ def del_if_exists(t, d):
96
+ """Deletes a key from a dictionary if it exists.
97
+
98
+ Args:
99
+ t (string): target key to delete
100
+ d (dict): dictionary to delete from
101
+ """
102
+ if t in d:
103
+ del d[t]
104
+ return
105
+ for k, v in d.items():
106
+ if isinstance(v, collections.abc.Mapping):
107
+ del_if_exists(t, v)
108
+
109
+
110
+ def replace_dict(d, u, ignored_keys=[]):
111
+ """Replaces values in dict d with values in dict u.
112
+
113
+ Args:
114
+ d (dict): the target dict to overwrite
115
+ u (dict): the dict containing the values to overwrite the target dict
116
+
117
+ Returns:
118
+ dict d with values overwritten by the corresponding ones in dict u.
119
+ """
120
+ if u is not None:
121
+ for k, v in u.items():
122
+ if k not in ignored_keys:
123
+ if v is None:
124
+ del_if_exists(k, d)
125
+ continue
126
+ if isinstance(v, collections.abc.Mapping):
127
+ d[k] = replace_dict(d.get(k, {}), v, ignored_keys)
128
+ else:
129
+ d[k] = v
130
+ return d
131
+
132
+
133
+ def get_val_by_key(d: dict, k):
134
+ if k in d:
135
+ return d[k]
136
+ for v in d.values():
137
+ if isinstance(v, dict):
138
+ return get_val_by_key(v, k)
139
+ return None
140
+
141
+
142
+ def set_val_by_key(d: dict, k, vv):
143
+ if k in d:
144
+ d[k] = vv
145
+ for v in d.values():
146
+ if isinstance(v, dict):
147
+ set_val_by_key(v, k, vv)
148
+
149
+
150
+ def fetch_hostfile(hostfile_path):
151
+ if not os.path.isfile(hostfile_path):
152
+ logger.warning("Unable to find hostfile, will proceed with training "
153
+ "with local resources only.")
154
+ return None
155
+
156
+ # e.g., worker-0 slots=16
157
+ with open(hostfile_path, 'r') as fd:
158
+ resource_pool = collections.OrderedDict()
159
+ for line in fd.readlines():
160
+ line = line.strip()
161
+ if line == '':
162
+ # skip empty lines
163
+ continue
164
+ try:
165
+ hostname, slots = line.split()
166
+ _, slot_count = slots.split("=")
167
+ slot_count = int(slot_count)
168
+ except ValueError as err:
169
+ logger.error("Hostfile is not formatted correctly, unable to "
170
+ "proceed with training.")
171
+ raise err
172
+ if hostname in resource_pool:
173
+ logger.error("Hostfile contains duplicate hosts, unable to "
174
+ "proceed with training.")
175
+ raise ValueError("host {} is already defined".format(hostname))
176
+ resource_pool[hostname] = slot_count
177
+
178
+ return resource_pool
179
+
180
+
181
+ def validate_ds_config(config: dict):
182
+
183
+ def is_False(config: dict, key):
184
+ if config is None:
185
+ return False
186
+ return bool(config.get(key))
187
+
188
+ config_zero = config.get("zero_optimization", {})
189
+ if not config_zero:
190
+ return True
191
+ stage = config_zero.get("stage")
192
+ offload = False
193
+ if stage == 1:
194
+ return True
195
+ elif stage == 2:
196
+ if is_False(config_zero, "cpu_offload") and is_False(config_zero, "cpu_offload_params"):
197
+ return False
198
+ elif stage == 3:
199
+ offload_devices = ["cpu", "nvme"]
200
+ if config_zero.get("offload_optimizer", {}).get("device") in offload_devices:
201
+ offload = True
202
+ if config_zero.get("offload_param", {}).get("device") in offload_devices:
203
+ offload = True
204
+ else:
205
+ return True
206
+
207
+ # HF requires that "ZeRO Offload can only work with DeepSpeed optimizers"
208
+ if offload and not config.get("optimizer"):
209
+ return False
210
+
211
+ return True
212
+
213
+
214
+ def remove_dupe_dicts(l):
215
+ """ Removes duplicate dictionaries from a list. Uses list comprehension and the json library to sort and stringify each dictionary and the set data type to ensure unique values. Works with nested data structures.
216
+
217
+ Args:
218
+ l (list): a list of (nested) data structures.
219
+
220
+ Returns:
221
+ A list of unique values.
222
+ """
223
+ list_of_strings = [json.dumps(d, sort_keys=True) for d in l]
224
+ list_of_strings = set(list_of_strings)
225
+ return [json.loads(s) for s in list_of_strings]
226
+
227
+
228
+ def prune_config(config, ignored_keys=[]):
229
+ """ Prunes the input configurations
230
+
231
+ Args:
232
+ configs (dict): A configuration dictionary.
233
+ ignored_keys (list, optional): the keys of the sections to delete. Defaults to [].
234
+
235
+ Returns:
236
+ A configuration dictionary.
237
+ """
238
+ if ignored_keys:
239
+ for k in ignored_keys:
240
+
241
+ def find_del_key(d: dict, k: str):
242
+ if k in d:
243
+ del d[k]
244
+ else:
245
+ for dd in d.values():
246
+ if isinstance(dd, dict):
247
+ find_del_key(dd, k)
248
+
249
+ find_del_key(config, k)
250
+
251
+
252
+ def prune_configs(configs, ignored_keys=[]):
253
+ """ Prunes the input list of configurations
254
+
255
+ Args:
256
+ configs (list): A list of configuration dictionaries.
257
+ ignored_keys (list, optional): the keys of the sections to delete. Defaults to [].
258
+
259
+ Returns:
260
+ A list of valid and unique configuration dictionaries.
261
+ """
262
+ pruned_list = []
263
+ for config in configs:
264
+ prune_config(config, ignored_keys)
265
+ pruned_list.append(config)
266
+
267
+ return remove_dupe_dicts(pruned_list)
268
+
269
+
270
+ def get_tuning_keys(tuning_space: dict):
271
+ """Outputs the list of tunable parameters in the tuning space dict.
272
+
273
+ Args:
274
+ tuning_space (dict): a configuration dictionary containing tunable parameters as lists of values.
275
+
276
+ Returns:
277
+ A list of strings
278
+ """
279
+ tuning_keys = []
280
+ for key, val in tuning_space.items():
281
+ if isinstance(val, dict):
282
+ tuning_keys.extend(get_tuning_keys(val))
283
+ if isinstance(val, list) and len(val) > 1:
284
+ tuning_keys.append(key)
285
+ return tuning_keys
286
+
287
+
288
+ def get_all_configs(tuning_space: dict, ignore_keys=None):
289
+ """ Splits the tuning space dictionary to result in all combinations of values.
290
+
291
+ Args:
292
+ tuning_space (dict): the tuning space where tunable parameters are lists of values.
293
+ """
294
+
295
+ def gen_combinations(d: dict):
296
+ keys, values = d.keys(), d.values()
297
+ for v in values:
298
+ if not isinstance(v, list):
299
+ v = [v]
300
+ values_choices = (gen_combinations(v) if isinstance(v, dict) else get_list(v) for v in values)
301
+ for comb in itertools.product(*values_choices):
302
+ yield dict(zip(keys, comb))
303
+
304
+ all_configs = []
305
+ ignored_key_vals = {}
306
+ for ik in ignore_keys:
307
+ ignored_key_vals[ik] = tuning_space.get(ik, {})
308
+ del_if_exists(ik, tuning_space)
309
+ for c in gen_combinations(tuning_space):
310
+ replace_dict(c, ignored_key_vals)
311
+ all_configs.append(c)
312
+ return all_configs
313
+
314
+
315
+ def canonical_name(config: dict, tuning_keys=None, prefix="", omit_val=False):
316
+ """ Generates a name from the acronyms of the tuning keys in the config dict. TRAIN_MICRO_BATCH_SIZE_PER_GPU is always included in the tuning keys.
317
+ Args:
318
+ config (dict): the config dict used to generate the name
319
+ tuning_keys (list, optional): the tuning keys used to generate the name. Defaults to None.
320
+ prefix (str, optional): a string added to the beginning of the name. Defaults to None.
321
+ """
322
+ if TRAIN_MICRO_BATCH_SIZE_PER_GPU not in tuning_keys:
323
+ tuning_keys.append(TRAIN_MICRO_BATCH_SIZE_PER_GPU)
324
+ if GRADIENT_ACCUMULATION_STEPS not in tuning_keys:
325
+ tuning_keys.append(GRADIENT_ACCUMULATION_STEPS)
326
+ tuning_keys.sort()
327
+
328
+ def get_offload_name(offload_config):
329
+ cname = ""
330
+ if offload_config is None:
331
+ return "None_"
332
+ for key, val in offload_config.items():
333
+ key = "".join(map(lambda c: c[0], key.split('_')))
334
+ if (isinstance(val, int) or isinstance(val, float)) and val > 9000:
335
+ cname += key + '{:.1e}'.format(val) + "_"
336
+ else:
337
+ if isinstance(val, bool):
338
+ val = "T" if val else "F"
339
+ cname += f"{key}{val}_"
340
+ return cname
341
+
342
+ def get_name_by_keys(config: dict, tuning_keys=None, omit_val=False):
343
+ cname = ""
344
+ if not tuning_keys or config is None:
345
+ return cname
346
+ for key, val in config.items():
347
+ # skip the arg_mappings section when naming the exp file
348
+ if key == "arg_mappings":
349
+ continue
350
+ if key == "offload_param":
351
+ cname += "op_"
352
+ if not omit_val:
353
+ cname += get_offload_name(val)
354
+ continue
355
+ if key == "offload_optimizer":
356
+ cname += "oo_"
357
+ if not omit_val:
358
+ cname += get_offload_name(val)
359
+ continue
360
+ # recursively call the func to get name for the child dicts
361
+ if isinstance(val, dict):
362
+ n = get_name_by_keys(val, tuning_keys, omit_val=omit_val)
363
+ if n != "":
364
+ cname += n + "_"
365
+ if tuning_keys and key not in tuning_keys:
366
+ continue
367
+
368
+ key_str = "".join(map(lambda c: c[0], key.split('_')))
369
+
370
+ if not omit_val:
371
+ if (isinstance(val, int) or isinstance(val, float)) and val > 9000:
372
+ cname += key_str + '{:.1e}'.format(val) + "_"
373
+ else:
374
+ if isinstance(val, bool):
375
+ val = "T" if val else "F"
376
+ cname += f"{key_str}{val}_"
377
+ else:
378
+ cname += key_str + "_"
379
+
380
+ return cname[:-1]
381
+
382
+ name = get_name_by_keys(config, tuning_keys, omit_val=omit_val)
383
+
384
+ return prefix + (name if name != "" else "exp")
385
+
386
+
387
+ def get_first_config(config: dict):
388
+ if not config:
389
+ return None
390
+ cfg = copy.deepcopy(config)
391
+
392
+ for key, val in cfg.items():
393
+ if isinstance(val, dict):
394
+ if key == "optimizer": # use user defined optimizer which might have lists of values as params
395
+ cfg[key] = val
396
+ else:
397
+ cfg[key] = get_first_config(val)
398
+ if isinstance(val, list) and len(val) > 0:
399
+ cfg[key] = val[0]
400
+ return cfg
401
+
402
+
403
+ def write_experiments(exps: list, exps_dir: str):
404
+ exp_paths = []
405
+ for exp in exps:
406
+ exp_name = exp['name']
407
+ # write the expr config to a json file
408
+ exp_path = os.path.join(exps_dir, f'{exp_name}.json')
409
+ with open(exp_path, 'w') as fd:
410
+
411
+ json.dump(exp, fd)
412
+ exp_paths.append(exp_path)
413
+ return exp_paths
414
+
415
+
416
+ def memory_to_string(n, postfix="", units=None, precision=2):
417
+ if units is None:
418
+ if n // 10**12 > 0:
419
+ return str(round(n / 1024**4, precision)) + " T" + postfix
420
+ if n // 10**9 > 0:
421
+ return str(round(n / 1024**3, precision)) + " G" + postfix
422
+ elif n // 10**6 > 0:
423
+ return str(round(n / 1024**2, precision)) + " M" + postfix
424
+ elif n // 10**3 > 0:
425
+ return str(round(n / 1014, precision)) + " K" + postfix
426
+ else:
427
+ return str(n) + " "
428
+ else:
429
+ if units == "T":
430
+ return str(round(n / 1024**4, precision)) + " " + units
431
+ if units == "G" + postfix:
432
+ return str(round(n / 1024**3, precision)) + " " + units
433
+ elif units == "M" + postfix:
434
+ return str(round(n / 1024**2, precision)) + " " + units
435
+ elif units == "K" + postfix:
436
+ return str(round(n / 1024, precision)) + " " + units
437
+ else:
438
+ return str(n) + " "
439
+
440
+
441
+ def number_to_string(n, postfix="", units=None, precision=2):
442
+ if units is None:
443
+ if n // 10**9 > 0:
444
+ return str(round(n / 1000**3, precision)) + " B" + postfix
445
+ if n // 10**6 > 0:
446
+ return str(round(n / 1000**2, precision)) + " M" + postfix
447
+ elif n // 10**3 > 0:
448
+ return str(round(n / 1000**1, precision)) + " K" + postfix
449
+ else:
450
+ return str(n) + " "
451
+ else:
452
+ if units == "B" + postfix:
453
+ return str(round(n / 1000**3, precision)) + " " + units
454
+ elif units == "M" + postfix:
455
+ return str(round(n / 1000**2, precision)) + " " + units
456
+ elif units == "K" + postfix:
457
+ return str(round(n / 1000**1, precision)) + " " + units
458
+ else:
459
+ return str(n) + " "
lib/python3.12/site-packages/deepspeed/checkpoint/__init__.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ from .reshape_meg_2d import reshape_meg_2d_parallel
7
+
8
+ from .deepspeed_checkpoint import DeepSpeedCheckpoint
9
+
10
+ from .utils import (get_layer_ckpt_name_for_rank, get_model_ckpt_name_for_rank, get_zero_ckpt_name_for_rank)
11
+
12
+ from .reshape_utils import (merge_state)
13
+
14
+ from .reshape_3d_utils import (model_3d_desc, get_model_3d_descriptor)
15
+
16
+ from .zero_checkpoint import ZeROCheckpoint
17
+
18
+ from .universal_checkpoint import enable_universal_checkpoint, SubparamShape
19
+
20
+ from .constants import *
lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (790 Bytes). View file
 
lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/constants.cpython-312.pyc ADDED
Binary file (2.54 kB). View file
 
lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/deepspeed_checkpoint.cpython-312.pyc ADDED
Binary file (20.1 kB). View file
 
lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/ds_to_universal.cpython-312.pyc ADDED
Binary file (28.8 kB). View file
 
lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/reshape_3d_utils.cpython-312.pyc ADDED
Binary file (6.3 kB). View file
 
lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/reshape_meg_2d.cpython-312.pyc ADDED
Binary file (9.05 kB). View file
 
lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/reshape_utils.cpython-312.pyc ADDED
Binary file (6.03 kB). View file
 
lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/universal_checkpoint.cpython-312.pyc ADDED
Binary file (5.87 kB). View file
 
lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/utils.cpython-312.pyc ADDED
Binary file (3.13 kB). View file
 
lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/zero_checkpoint.cpython-312.pyc ADDED
Binary file (8.18 kB). View file
 
lib/python3.12/site-packages/deepspeed/checkpoint/constants.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+ """
6
+ Various symbolic constants used for model checkpointing
7
+ """
8
+
9
+ #########################################
10
+ # Optimizer checkpoint keys
11
+ #########################################
12
+ OPTIMIZER_STATE_DICT = "optimizer_state_dict"
13
+ FP32_GROUPS = "fp32_groups"
14
+ FP32_FLAT_GROUPS = 'fp32_flat_groups'
15
+
16
+ BASE_OPTIMIZER_STATE = 'base_optimizer_state'
17
+ BASE_OPTIMIZER_STATE_STEP = 'base_optimizer_state_step'
18
+ SINGLE_PARTITION_OF_FP32_GROUPS = "single_partition_of_fp32_groups"
19
+ PARAM_GROUPS = 'param_groups'
20
+ GROUP_PADDINGS = 'group_paddings'
21
+ PARTITION_COUNT = 'partition_count'
22
+ ZERO_STAGE = 'zero_stage'
23
+ CLIP_GRAD = 'clip_grad'
24
+ FP32_WEIGHT_KEY = "fp32"
25
+ LOSS_SCALER = 'loss_scaler'
26
+
27
+ #########################################
28
+ # Module checkpoint keys
29
+ #########################################
30
+ PARAM = 'param'
31
+ PARAM_SHAPES = 'param_shapes'
32
+ BUFFER_NAMES = 'buffer_names'
33
+ FROZEN_PARAM_SHAPES = 'frozen_param_shapes'
34
+ FROZEN_PARAM_FRAGMENTS = 'frozen_param_fragments'
35
+
36
+ #########################################
37
+ # Checkpoint naming constants
38
+ #########################################
39
+ MODEL_FILE_PREFIX = 'mp_rank_'
40
+ ZERO_FILE_PREFIX = 'zero_pp_rank_'
41
+ OPTIM_FILE_SUFFIX = '_optim_states.pt'
42
+ MODEL_FILE_SUFFIX = '_model_states.pt'
43
+ LAYER_FILE_PREFIX = 'layer_'
44
+ BF16_ZERO_FILE_PREFIX = 'bf16_' + ZERO_FILE_PREFIX
45
+ FP16_ZERO_FILE_PREFIX = 'fp16_' + ZERO_FILE_PREFIX
46
+
47
+ #########################################
48
+ # Checkpoint utility keys
49
+ #########################################
50
+ DS_VERSION = 'ds_version'
51
+
52
+ #########################################
53
+ # Universal Checkpoint keys
54
+ #########################################
55
+ UNIVERSAL_CHECKPOINT_INFO = 'universal_checkpoint_info'
56
+ UNIVERSAL_CHECKPOINT_VERSION_KEY = 'universal_checkpoint_version'
57
+ # Reserve version 0.1 for the hardcoded logic used in BLOOM-176B training
58
+ UNIVERSAL_CHECKPOINT_VERSION_VALUE = 0.2
59
+
60
+ # Vocabulary padding
61
+ VOCAB_TENSOR = 'vocab_tensor'
62
+ PADDED_VOCAB_SIZE = 'padded_vocab_size'
63
+ ORIGINAL_VOCAB_SIZE = 'original_vocab_size'
64
+
65
+ # Parameter splitting/merging
66
+ PARAM_SLICE_MAPPINGS = 'param_slice_mappings'
67
+ CAT_DIM = "cat_dim"
68
+ # Following is a special case where a parameter effectively contains sub parameters.
69
+ # As an example, consider Megatron-DeepSpeed GPT SWIGLU implementation (mlp.h_to_4h).
70
+ # In this case, a single parameter ia allocated contiguously, but used as separate parameters.
71
+ # When using universal checkpoint, we have to normalize the representation of the full parameter.
72
+ # We normalize it by concatenating all slices of the sub params and then concatenating the sub params.
73
+ # All concat operations are done on CAT_DIM (currently, no support for different concat dims sub params and TP slicing).
74
+ # Similarly, load_hp_checkpoint_state has to take the needed actions when loading from universal.
75
+ PARAM_N_SUB_PARAMS = "param_n_sub_params"
76
+
77
+ SUB_PARAM_SHAPE = "sub_param_shape"
78
+
79
+ # Regex list of parameters that require special handling
80
+ VOCABULARY_PARAMETER_PATTERNS = 'vocabulary_parameter_patterns'
81
+ PIPELINE_REPLICATED_PARAMETER_PATTERNS = 'pipeline_replicated_parameter_patterns'
82
+ PARAMETER_TO_AVERAGE_PATTERNS = 'parameter_to_average_patterns'
83
+ PARAMETER_WITH_ROW_PARALLELISM_PATTERNS = 'parameter_with_row_parallelism_patterns'
84
+ TP_REPLICATED_PARAMETER_PATTERNS = 'tp_replicated_parameter_patterns'
85
+ PARAMETER_WITH_2_SUB_PARAMS_CAT_DIM_0 = 'parameter_with_2_sub_params_cat_dim_0'
86
+ PARAMETER_WITH_SUB_PARAMS = 'parameter_with_sub_params'
87
+ SUB_PARAMS_SHAPE = 'sub_params_shape'
lib/python3.12/site-packages/deepspeed/checkpoint/deepspeed_checkpoint.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ import os
7
+ import re
8
+ from typing import Dict
9
+ import torch
10
+
11
+ from .reshape_3d_utils import model_3d_desc
12
+ from .reshape_utils import (basic_folder_validation, merge_state, partition_data, get_files, get_files_with_prefix)
13
+
14
+ from .constants import (MODEL_FILE_PREFIX, LAYER_FILE_PREFIX)
15
+
16
+ from .reshape_meg_2d import reshape_meg_2d_parallel, meg_2d_parallel_map
17
+ from .zero_checkpoint import ZeROCheckpoint
18
+ from .constants import *
19
+
20
+ EMBEDDING_LAYER_INDEX = 0
21
+ FINAL_LAYER_NORM_INDEX = -1
22
+ ARGS_KEY = 'args'
23
+ CHECKPOINT_INFO_KEY = 'checkpoint_info'
24
+ ITERATION_KEY = 'iteration'
25
+ LAYER_FILE_PREFIX_PATTERN = r'layer_(\d+)-model_.*'
26
+
27
+ SEQUENTIAL_LAYERS = [
28
+ 'input_layernorm.weight', 'input_layernorm.bias', 'self_attention.dense.bias', 'post_attention_layernorm.weight',
29
+ 'post_attention_layernorm.bias', 'mlp.dense_4h_to_h.bias', 'position_embeddings.weight'
30
+ ]
31
+
32
+ LAYER_CONCAT_DIM = {'self_attention.dense.weight': 1, 'mlp.dense_4h_to_h.weight': 1}
33
+
34
+
35
+ class DeepSpeedCheckpoint(object):
36
+
37
+ def __init__(self,
38
+ dir,
39
+ tp_degree=None,
40
+ pp_degree=None,
41
+ dp_degree=None,
42
+ final_layer_norm_idx=FINAL_LAYER_NORM_INDEX):
43
+ self.final_layer_norm_idx = final_layer_norm_idx
44
+ self.dir = dir
45
+
46
+ pipeline_parallel = len(get_files_with_prefix(get_files(dir), LAYER_FILE_PREFIX)) > 0
47
+
48
+ self._validate_folder(dir, pipeline_parallel)
49
+
50
+ self.zero_checkpoint = ZeROCheckpoint(dir)
51
+
52
+ self.file_list = get_files(dir)
53
+ self.layer_files = get_files_with_prefix(self.file_list, LAYER_FILE_PREFIX)
54
+ self.mp_rank_files = get_files_with_prefix(self.file_list, MODEL_FILE_PREFIX)
55
+
56
+ self.layer_keys = self._get_layer_keys()
57
+ self.layer_count = len(self.layer_keys)
58
+
59
+ self.tp_degree = self.zero_checkpoint.get_src_tp_degree() if tp_degree is None else tp_degree
60
+ self.pp_degree = self.zero_checkpoint.get_src_pp_degree() if pp_degree is None else pp_degree
61
+ self.dp_degree = self.zero_checkpoint.get_src_dp_degree() if dp_degree is None else dp_degree
62
+
63
+ self.original_world_size = self.zero_checkpoint.get_src_tp_degree() * self.zero_checkpoint.get_src_pp_degree(
64
+ ) * self.zero_checkpoint.get_src_dp_degree()
65
+ self.world_size = self.tp_degree * self.pp_degree * self.dp_degree
66
+
67
+ self.old_2d_map = meg_2d_parallel_map(self.zero_checkpoint.get_src_pp_degree(),
68
+ self.zero_checkpoint.get_src_tp_degree())
69
+ self.old_2d_map.simple_init()
70
+ self.new_2d_map = reshape_meg_2d_parallel(old_pp_degree=self.zero_checkpoint.get_src_pp_degree(),
71
+ old_tp_degree=self.zero_checkpoint.get_src_tp_degree(),
72
+ new_pp_degree=self.pp_degree,
73
+ new_tp_degree=self.tp_degree)
74
+
75
+ if self.is_change_pp_degree() or self.is_change_tp_degree() or self.is_change_dp_degree():
76
+ self.zero_checkpoint.reshape(model_3d_desc(self.pp_degree, self.tp_degree, self.dp_degree))
77
+
78
+ self.global_state = {}
79
+
80
+ self._sanity_check()
81
+ self.pp_to_transformer_map = self._build_pp_transformer_map()
82
+ self.transformer_file_map = self._build_transformer_file_map()
83
+ self.tp_to_embedding_map = self._build_tp_other_layer_map(EMBEDDING_LAYER_INDEX)
84
+ self.tp_to_final_norm_map = self._build_tp_other_layer_map(self.final_layer_norm_idx)
85
+ self._build_global_state()
86
+
87
+ def is_change_tp_degree(self):
88
+ return self.tp_degree != self.zero_checkpoint.get_src_tp_degree()
89
+
90
+ def is_change_pp_degree(self):
91
+ return self.pp_degree != self.zero_checkpoint.get_src_pp_degree()
92
+
93
+ def is_change_dp_degree(self):
94
+ return self.dp_degree != self.zero_checkpoint.get_src_dp_degree()
95
+
96
+ def show_2d_mapping(self):
97
+ print(f'reshaped 2d map ---- begin')
98
+
99
+ for i in range(self.pp_degree):
100
+ for j in range(self.tp_degree):
101
+ file_list = self.get_2d_parallel_files(pp_index=i, tp_index=j)
102
+ print(f'[{i}, {j}] = {file_list}')
103
+
104
+ print(f'reshaped 2d map ---- end')
105
+
106
+ def show_tp_embedding_map(self):
107
+ self._dump_mapping(self.tp_to_embedding_map, 'tp_to_embedding_layers')
108
+
109
+ def show_tp_final_norm_map(self):
110
+ self._dump_mapping(self.tp_to_final_norm_map, 'tp_to_final_norm_layers')
111
+
112
+ def show_pp_transformer_map(self):
113
+ self._dump_mapping(self.pp_to_transformer_map, 'pp_to_transformer_layers')
114
+
115
+ def show_transformer_file_map(self):
116
+ self._dump_mapping(self.transformer_file_map, 'rank_to_transformer_files')
117
+
118
+ def _build_global_state(self):
119
+ sd = torch.load(self.mp_rank_files[0], map_location=torch.device('cpu'), weights_only=False)
120
+ self.global_state[ITERATION_KEY] = sd.get(ITERATION_KEY, 0)
121
+ self.global_state[ARGS_KEY] = sd.get(ARGS_KEY, None)
122
+
123
+ def get_zero_checkpoint_state(self, pp_index, tp_index, dp_index) -> dict:
124
+ return self.zero_checkpoint.get_state_for_rank(pp_index=pp_index,
125
+ tp_index=tp_index,
126
+ dp_index=dp_index,
127
+ keys_to_ignore=[PARAM_SHAPES])
128
+
129
+ def get_zero_files(self, pp_index, tp_index, dp_index) -> list:
130
+ return self.zero_checkpoint.get_files_for_rank(pp_index=pp_index, tp_index=tp_index, dp_index=dp_index)
131
+
132
+ def get_embedding_layer_id(self):
133
+ return self.layer_keys[EMBEDDING_LAYER_INDEX]
134
+
135
+ def get_final_norm_layer_id(self):
136
+ return self.layer_keys[self.final_layer_norm_idx]
137
+
138
+ def get_iteration(self):
139
+ if not ITERATION_KEY in self.global_state:
140
+ sd = torch.load(self.mp_rank_files[0], map_location=torch.device('cpu'), weights_only=False)
141
+ self.global_state[ITERATION_KEY] = sd.get(ITERATION_KEY, 0)
142
+
143
+ return self.global_state[ITERATION_KEY]
144
+
145
+ def get_embedding_state(self, tp_index: int) -> Dict:
146
+ assert tp_index in self.tp_to_embedding_map.keys()
147
+ sd_list = [
148
+ torch.load(fname, map_location=torch.device('cpu'), weights_only=False)
149
+ for fname in self.tp_to_embedding_map[tp_index]
150
+ ]
151
+ sd = self._merge_state_dicts(sd_list)
152
+ return sd
153
+
154
+ def get_embedding_files(self, tp_index: int) -> list:
155
+ assert tp_index in self.tp_to_embedding_map.keys()
156
+ return self.tp_to_embedding_map[tp_index]
157
+
158
+ def _get_checkpoint_value(self, key):
159
+ if not key in self.global_state:
160
+ sd = torch.load(self.mp_rank_files[0], map_location=torch.device('cpu'), weights_only=False)
161
+ self.global_state[key] = sd.get(key, None)
162
+
163
+ return self.global_state[key]
164
+
165
+ def get_args(self):
166
+ return self._get_checkpoint_value(ARGS_KEY)
167
+
168
+ def get_checkpoint_info(self, info_key=CHECKPOINT_INFO_KEY):
169
+ return self._get_checkpoint_value(info_key)
170
+
171
+ def get_2d_parallel_state(self, tp_index: int, pp_index: int) -> dict:
172
+ assert tp_index < self.tp_degree
173
+ assert pp_index < self.pp_degree
174
+ fname_list = self.get_2d_parallel_files(tp_index=tp_index, pp_index=pp_index)
175
+ sd_list = [torch.load(fname, map_location=torch.device('cpu'), weights_only=False) for fname in fname_list]
176
+
177
+ merged_sd = None
178
+ for sd in sd_list:
179
+ if merged_sd is None:
180
+ merged_sd = sd
181
+ else:
182
+ merged_sd = merge_state(merged_sd, sd)
183
+
184
+ return merged_sd
185
+
186
+ def get_transformer_state(self, tp_index: int, pp_index: int) -> list:
187
+ assert tp_index < self.tp_degree
188
+ assert pp_index < self.pp_degree
189
+ t_list = []
190
+ for fname_list in self.transformer_file_map[(tp_index, pp_index)]:
191
+ sd_list = [torch.load(fname, map_location=torch.device('cpu'), weights_only=False) for fname in fname_list]
192
+ sd = self._merge_state_dicts(sd_list)
193
+ t_list.append(sd)
194
+ return t_list
195
+
196
+ def get_pp_transformer_map(self, pp_index: int) -> list:
197
+ assert pp_index < self.pp_degree
198
+ return self.pp_to_transformer_map[pp_index]
199
+
200
+ def get_final_norm_state(self, tp_index: int) -> Dict:
201
+ assert tp_index in self.tp_to_final_norm_map.keys()
202
+ sd = torch.load(self.tp_to_final_norm_map[tp_index][0], map_location=torch.device('cpu'), weights_only=False)
203
+ return sd
204
+
205
+ def get_final_norm_files(self, tp_index: int) -> list:
206
+ assert tp_index in self.tp_to_final_norm_map.keys()
207
+ return self.tp_to_final_norm_map[tp_index]
208
+
209
+ def _build_tp_other_layer_map(self, layer_index: int):
210
+ data_map = {}
211
+ if len(self.layer_files) < 1:
212
+ return data_map
213
+ assert layer_index <= len(self.layer_files)
214
+ layer_files = get_files_with_prefix(self.layer_files, self.layer_keys[layer_index])
215
+ layer_file_partitions = partition_data(layer_files, self.tp_degree)
216
+ data_map = {i: flist for i, flist in enumerate(layer_file_partitions)}
217
+ return data_map
218
+
219
+ def get_2d_parallel_files(self, tp_index: int, pp_index: int) -> list:
220
+ assert tp_index < self.tp_degree
221
+ assert pp_index < self.pp_degree
222
+ file_indices = self.new_2d_map.get_data(pp_index=pp_index, tp_index=tp_index)
223
+ return [self.mp_rank_files[i] for i in file_indices]
224
+
225
+ def _build_pp_transformer_map(self):
226
+ data_map = {}
227
+ if self.pp_degree > 0:
228
+ transformer_layers = self.layer_keys[1:self.final_layer_norm_idx]
229
+ layers_per_pp = len(transformer_layers) // self.pp_degree
230
+ data_map = {
231
+ i: transformer_layers[i * layers_per_pp:(i + 1) * layers_per_pp]
232
+ for i in range(0, self.pp_degree)
233
+ }
234
+ return data_map
235
+
236
+ def _dump_mapping(self, data_map, map_tag=None):
237
+ if map_tag is not None:
238
+ print(f'Dump mapping: {map_tag}')
239
+ for k, v in data_map.items():
240
+ print(f'{k} = {v}')
241
+
242
+ def _build_transformer_file_map(self):
243
+ transformer_layer_keys = self.layer_keys[1:self.final_layer_norm_idx]
244
+ file_map = {}
245
+ # XXX: this is not guaranteed
246
+ layers_per_pp = 1
247
+ if self.pp_degree > 0:
248
+ layers_per_pp = len(transformer_layer_keys) // self.pp_degree
249
+ #print(f"{transformer_layer_keys} {layers_per_pp}")
250
+ for key_index, layer_key in enumerate(transformer_layer_keys):
251
+ pp_index = key_index // layers_per_pp
252
+ layer_files = get_files_with_prefix(self.layer_files, layer_key + '-')
253
+ layer_file_partitions = partition_data(layer_files, self.tp_degree)
254
+ for tp_index in range(self.tp_degree):
255
+ map_key = (tp_index, pp_index)
256
+ if not map_key in file_map.keys():
257
+ file_map[map_key] = []
258
+ file_map[map_key].append(layer_file_partitions[tp_index])
259
+
260
+ return file_map
261
+
262
+ def _sanity_check(self):
263
+ assert len(self.mp_rank_files) % self.tp_degree == 0
264
+ assert self.zero_checkpoint.num_files % (self.pp_degree * self.tp_degree) == 0
265
+ assert self.zero_checkpoint.num_files % (self.tp_degree) == 0
266
+ # XXX: fix me - isn't always the case
267
+ # only true with --pp-partition-method 'type:transformer|embedding' \
268
+ # assert (len(self.layer_keys) - 2) % self.pp_degree == 0
269
+
270
+ def validate_files(self):
271
+ for file in self.file_list:
272
+ if not os.path.isfile(file):
273
+ print(f'Error: {file} is not existent')
274
+
275
+ def _get_layer_keys(self):
276
+ key_set = set()
277
+ for file_path in self.layer_files:
278
+ _, fname = os.path.split(file_path)
279
+ layer_id = re.search(LAYER_FILE_PREFIX_PATTERN, fname).group(1)
280
+ key_set.add(layer_id)
281
+ sorted_ids = sorted(list(key_set), key=int)
282
+ layer_keys = [LAYER_FILE_PREFIX + str(layer_id) for layer_id in sorted_ids]
283
+ return layer_keys
284
+
285
+ def _merge_state_dicts(self, sd_list):
286
+ merged_sd = {}
287
+ for key in sd_list[0].keys():
288
+ if not key in SEQUENTIAL_LAYERS:
289
+ cat_dim = LAYER_CONCAT_DIM.get(key, 0)
290
+ merged_sd[key] = torch.cat([sd[key] for sd in sd_list], dim=cat_dim)
291
+ else:
292
+ merged_sd[key] = sd_list[0][key]
293
+
294
+ return merged_sd
295
+
296
+ def _validate_folder(self, dir, pipeline_parallel):
297
+ basic_folder_validation(dir)
298
+
299
+ file_list = get_files(dir)
300
+ file_prefix_list = [MODEL_FILE_PREFIX]
301
+ if pipeline_parallel:
302
+ file_prefix_list.extend([LAYER_FILE_PREFIX, f'{LAYER_FILE_PREFIX}01'])
303
+ for file_prefix in file_prefix_list:
304
+ ckpt_files = get_files_with_prefix(file_list, file_prefix)
305
+ assert len(
306
+ ckpt_files
307
+ ) > 0, f'{dir} seems a bogus DeepSpeed checkpoint folder: Cannot find {file_prefix}* files in there.'
lib/python3.12/site-packages/deepspeed/checkpoint/ds_to_universal.py ADDED
@@ -0,0 +1,549 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+
3
+ # Copyright (c) Microsoft Corporation.
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ # DeepSpeed Team
7
+
8
+ from functools import partial
9
+ from itertools import chain
10
+ import argparse
11
+ import glob
12
+ import itertools
13
+ import math
14
+ from concurrent.futures import ProcessPoolExecutor
15
+ import os
16
+ import re
17
+ import shutil
18
+ import torch
19
+ import tqdm
20
+ #from pprint import pprint
21
+
22
+ from deepspeed.checkpoint import DeepSpeedCheckpoint
23
+ from deepspeed.checkpoint import (
24
+ OPTIMIZER_STATE_DICT,
25
+ ZERO_STAGE,
26
+ BASE_OPTIMIZER_STATE,
27
+ SINGLE_PARTITION_OF_FP32_GROUPS,
28
+ PARAM_GROUPS,
29
+ PARAM_SLICE_MAPPINGS,
30
+ PARAM_SHAPES,
31
+ PARAM,
32
+ CAT_DIM,
33
+ PARAM_N_SUB_PARAMS,
34
+ SUB_PARAM_SHAPE,
35
+ VOCAB_TENSOR,
36
+ UNIVERSAL_CHECKPOINT_INFO,
37
+ UNIVERSAL_CHECKPOINT_VERSION_KEY,
38
+ UNIVERSAL_CHECKPOINT_VERSION_VALUE,
39
+ VOCABULARY_PARAMETER_PATTERNS,
40
+ PIPELINE_REPLICATED_PARAMETER_PATTERNS,
41
+ TP_REPLICATED_PARAMETER_PATTERNS,
42
+ PARAMETER_TO_AVERAGE_PATTERNS,
43
+ PARAMETER_WITH_ROW_PARALLELISM_PATTERNS,
44
+ PARAMETER_WITH_2_SUB_PARAMS_CAT_DIM_0,
45
+ PARAMETER_WITH_SUB_PARAMS,
46
+ SubparamShape,
47
+ )
48
+
49
+
50
+ def parse_arguments():
51
+ parser = argparse.ArgumentParser()
52
+ parser.add_argument('--input_folder', type=str, required=True, help='Input DeepSpeed Checkpoint folder')
53
+ parser.add_argument('--output_folder', type=str, required=True, help='Output DeepSpeed checkpoint folder')
54
+ parser.add_argument('--num_extract_workers',
55
+ default=4,
56
+ type=int,
57
+ help='How many parallel processes to extract zero shards')
58
+ parser.add_argument(
59
+ '--num_merge_workers',
60
+ default=2,
61
+ type=int,
62
+ help=
63
+ 'How many parallel processes to merge tp slices (more memory intensive, use much fewer than --num_extract_workers))'
64
+ )
65
+ parser.add_argument('--keep_temp_folder',
66
+ action='store_true',
67
+ help='Preserve temporary folder of intermediate checkpoint slice files. Useful for debugging.')
68
+ parser.add_argument('--no_strict',
69
+ dest='strict',
70
+ action='store_false',
71
+ help='Do not perform validity checks on converted checkpoint.')
72
+ parser.add_argument('--inject_missing_state',
73
+ action='store_true',
74
+ help='Inject missing checkpoint state into the checkpoint if it is absent.')
75
+ args = parser.parse_args()
76
+ print(f'args = {args}')
77
+ return args
78
+
79
+
80
+ def atoi(text):
81
+ return int(text) if text.isdigit() else text
82
+
83
+
84
+ def natural_keys(text):
85
+ '''
86
+ alist.sort(key=natural_keys) sorts in human order
87
+ http://nedbatchelder.com/blog/200712/human_sorting.html
88
+ (See Toothy's implementation in the comments)
89
+ '''
90
+ return [atoi(c) for c in re.split(r'(\d+)', text)]
91
+
92
+
93
+ def _create_checkpoint_paths(base_folder, iteration, tp_degree, pp_degree):
94
+ path_list = []
95
+ iter_folder = f'iter_{iteration:07d}'
96
+ for i in range(0, tp_degree):
97
+ path_list.append([])
98
+ for j in range(0, pp_degree):
99
+ rank_folder = f'mp_rank_{i:02d}' if pp_degree == 1 else f'mp_rank_{i:02d}_{j:03d}'
100
+ ckpt_path = os.path.join(rank_folder, 'model_optim_rng.pt')
101
+ path_list[i].append(os.path.join(base_folder, iter_folder, ckpt_path))
102
+
103
+ return path_list
104
+
105
+
106
+ def _save_checkpoint(file_path, chkpt_sd):
107
+ dir, _ = os.path.split(file_path)
108
+ os.makedirs(dir, exist_ok=True)
109
+ torch.save(chkpt_sd, file_path)
110
+
111
+
112
+ def extract_zero_shards(dir, ds_checkpoint, indices_3D):
113
+ pp_index, tp_index, dp_index = indices_3D
114
+ sd = ds_checkpoint.get_zero_checkpoint_state(pp_index=pp_index, tp_index=tp_index, dp_index=dp_index)
115
+
116
+ # pprint(f"Processing {dp_index=} {pp_index=}, {tp_index=}")
117
+
118
+ optim_sd = sd[OPTIMIZER_STATE_DICT]
119
+ param_slice_mappings = optim_sd[PARAM_SLICE_MAPPINGS]
120
+ universal_checkpoint_info = ds_checkpoint.get_checkpoint_info(UNIVERSAL_CHECKPOINT_INFO)
121
+ pipeline_replicated_params = universal_checkpoint_info.get(PIPELINE_REPLICATED_PARAMETER_PATTERNS, [])
122
+ # print(f'{pipeline_replicated_params=}')
123
+
124
+ # dict
125
+ state_groups = optim_sd[BASE_OPTIMIZER_STATE]["state"]
126
+ # list
127
+ fp32_groups = optim_sd[SINGLE_PARTITION_OF_FP32_GROUPS]
128
+ param_groups_cnt = len(state_groups)
129
+
130
+ for param_group_id in range(param_groups_cnt):
131
+
132
+ flat_state = dict(
133
+ exp_avg=state_groups[param_group_id]["exp_avg"],
134
+ exp_avg_sq=state_groups[param_group_id]["exp_avg_sq"],
135
+ fp32=fp32_groups[param_group_id],
136
+ )
137
+
138
+ if "step" in state_groups[param_group_id]:
139
+ flat_state["step"] = state_groups[param_group_id]["step"]
140
+
141
+ for name, fragment_mapping in param_slice_mappings[param_group_id].items():
142
+ if pp_index > 0 and any(re.match(pattern, name) for pattern in pipeline_replicated_params):
143
+ # Skip tied weights that are replicated in first and last pp stages
144
+ continue
145
+
146
+ # pprint(f"dpt{dp_index}{pp_index}{tp_index} {param_group_id} {name} => {fragment_mapping.start}:{fragment_mapping.numel}")
147
+ for state_key in flat_state.keys():
148
+ dump_param_fragment(dir, tp_index, dp_index, state_key, flat_state[state_key], name,
149
+ fragment_mapping.start, fragment_mapping.numel)
150
+
151
+
152
+ def extract_zero_shards_stage3(optim_files, param_shapes, dp_degree, temp_dir, dp_index):
153
+ state_dict = torch.load(optim_files[dp_index], map_location='cpu', weights_only=False)
154
+
155
+ flat_state = dict(
156
+ exp_avg=state_dict[OPTIMIZER_STATE_DICT]['optimizer_state_dict']['state'][0]["exp_avg"],
157
+ exp_avg_sq=state_dict[OPTIMIZER_STATE_DICT]['optimizer_state_dict']['state'][0]["exp_avg_sq"],
158
+ fp32=state_dict[OPTIMIZER_STATE_DICT]['fp32_flat_groups'][0],
159
+ )
160
+
161
+ offset = 0
162
+ for name, shape in param_shapes.items():
163
+ unpartitioned_numel = shape.numel()
164
+ partitioned_numel, _ = _zero_partitioned_param_info(unpartitioned_numel, dp_degree)
165
+ padding_free_numel = min(partitioned_numel, abs(unpartitioned_numel - dp_index * partitioned_numel))
166
+ for state_key in flat_state.keys():
167
+ dump_param_fragment(temp_dir, 0, dp_index, state_key, flat_state[state_key], name, offset,
168
+ padding_free_numel)
169
+ offset += partitioned_numel
170
+
171
+
172
+ cnt = 0
173
+
174
+
175
+ def dp_index_to_str(dp_index):
176
+ return f"{dp_index:0>2d}"
177
+
178
+
179
+ def dump_param_fragment(dir, tp_index, dp_index, state_name, state_flat_tensor, param_name, offset, numel):
180
+
181
+ global cnt # temp hack
182
+
183
+ param_base_path = os.path.join(dir, param_name, str(tp_index))
184
+ os.makedirs(param_base_path, exist_ok=True)
185
+
186
+ cnt += 1
187
+
188
+ path = os.path.join(param_base_path, f"{state_name}.{dp_index_to_str(dp_index)}")
189
+
190
+ #print(f"{param_name}: {offset}: {numel} => {path}")
191
+
192
+ # State might be a python int or a tensor
193
+ if state_name != "step" and torch.is_tensor(state_flat_tensor):
194
+ state_flat_tensor = state_flat_tensor.narrow(0, offset, numel).clone()
195
+ _save_checkpoint(path, state_flat_tensor)
196
+
197
+
198
+ def _merge_zero_shards(param_base_path, state, tp_degree, slice_shape=None):
199
+ slices = []
200
+ for tp_index in range(tp_degree):
201
+ prefix_path = os.path.join(param_base_path, str(tp_index), f"{state}")
202
+ paths = glob.glob(f"{prefix_path}.*")
203
+
204
+ if len(paths) == 0:
205
+ continue
206
+
207
+ pattern = re.compile(f"{prefix_path}\\.([0-9]+)")
208
+ dp_indices = set()
209
+ for p in paths:
210
+ m = pattern.match(p)
211
+ if m:
212
+ dp_indices.add(int(m.group(1)))
213
+ else:
214
+ raise ValueError(f"Cannot parse dp_rank from {p}")
215
+
216
+ paths = [f"{prefix_path}.{dp_index_to_str(dp_index)}" for dp_index in sorted(list(dp_indices))]
217
+ shards = [torch.load(p, weights_only=False) for p in paths]
218
+
219
+ if state == "step":
220
+ assert all(v == shards[0] for v in shards), "All shards must have the same step value"
221
+ slice = shards[0]
222
+ else:
223
+ if slice_shape is None:
224
+ slice = torch.cat(shards, dim=0)
225
+ else:
226
+ slice = torch.cat(shards, dim=0).reshape(slice_shape)
227
+
228
+ slices.append(slice)
229
+ return slices
230
+
231
+
232
+ def merge_tp_slices(ds_checkpoint, dir, slice_dir, tp_degree, name_and_shape):
233
+
234
+ name, shape = name_and_shape
235
+ slice_base_path = os.path.join(slice_dir, name)
236
+ param_base_path = os.path.join(dir, name)
237
+
238
+ universal_checkpoint_info = ds_checkpoint.get_checkpoint_info(UNIVERSAL_CHECKPOINT_INFO)
239
+ replicated_parameters = universal_checkpoint_info.get(TP_REPLICATED_PARAMETER_PATTERNS, [])
240
+ parameters_to_average = universal_checkpoint_info.get(PARAMETER_TO_AVERAGE_PATTERNS, [])
241
+ parameters_with_row_parallelism = universal_checkpoint_info.get(PARAMETER_WITH_ROW_PARALLELISM_PATTERNS, [])
242
+ vocabulary_parameters = universal_checkpoint_info.get(VOCABULARY_PARAMETER_PATTERNS, [])
243
+ parameters_with_2_sub_params_cat_dim_0 = universal_checkpoint_info.get(PARAMETER_WITH_2_SUB_PARAMS_CAT_DIM_0, [])
244
+ parameter_with_sub_params = universal_checkpoint_info.get(PARAMETER_WITH_SUB_PARAMS, [])
245
+
246
+ unmatched_patterns = set(replicated_parameters + parameters_to_average + parameters_with_row_parallelism +
247
+ vocabulary_parameters + parameters_with_2_sub_params_cat_dim_0)
248
+ unmatched_patterns.update(chain.from_iterable(SubparamShape(**s).patterns for s in parameter_with_sub_params))
249
+
250
+ def get_matched_pattern(patterns_, name_):
251
+ matched_ = [pattern_ for pattern_ in patterns_ if re.match(pattern_, name_)]
252
+ assert len(matched_) <= 1, f'Got more than one matching patterns={matched_} for {name_}'
253
+ if matched_:
254
+ pattern_ = matched_[0]
255
+ unmatched_patterns.discard(pattern_)
256
+ return pattern_
257
+ return None
258
+
259
+ def get_matched_sub_params_pattern(name_):
260
+ for subparam_shape_dict in parameter_with_sub_params:
261
+ subparam_shape = SubparamShape(**subparam_shape_dict)
262
+ for pattern_ in subparam_shape.patterns:
263
+ if re.match(pattern_, name_):
264
+ unmatched_patterns.discard(pattern_)
265
+ return subparam_shape
266
+ return None
267
+
268
+ matched_sub_params_shape = get_matched_sub_params_pattern(name)
269
+
270
+ step_merged = _merge_zero_shards(slice_base_path, "step", tp_degree, shape)
271
+ if step_merged:
272
+ _save_checkpoint(os.path.join(param_base_path, f"step.pt"), step_merged[0])
273
+
274
+ for state in ("fp32", "exp_avg", "exp_avg_sq"):
275
+ slices = _merge_zero_shards(slice_base_path, state, tp_degree, shape)
276
+ final_path = os.path.join(param_base_path, f"{state}.pt")
277
+
278
+ #print(f"Expected shape: {shape}")
279
+ #print(f"Fragment sizes:", list(frag.shape for frag in slices))
280
+ ckpt_dict = {}
281
+ if get_matched_pattern(replicated_parameters, name):
282
+ if len(slices) > 1:
283
+ assert all([slices[0].equal(other_slice) for other_slice in slices[1:]])
284
+ param = slices[0]
285
+ # print(f'replicate {name} using first slice')
286
+ elif get_matched_pattern(parameters_to_average, name):
287
+ param = sum(slices) / len(slices)
288
+ # print(f'merge {name} using average')
289
+ elif get_matched_pattern(parameters_with_2_sub_params_cat_dim_0, name):
290
+ cat_dim = 0
291
+ chunked_slices = [torch.chunk(s, 2, dim=cat_dim) for s in slices]
292
+ merged_chunks_0 = torch.cat([s[0] for s in chunked_slices], dim=cat_dim)
293
+ merged_chunks_1 = torch.cat([s[1] for s in chunked_slices], dim=cat_dim)
294
+ param = torch.cat([merged_chunks_0, merged_chunks_1], dim=cat_dim)
295
+ ckpt_dict[CAT_DIM] = cat_dim
296
+ ckpt_dict[PARAM_N_SUB_PARAMS] = 2
297
+ elif matched_sub_params_shape:
298
+ merged_chunks = []
299
+ partition_dim = matched_sub_params_shape.partition_dim
300
+
301
+ sub_dim_sizes = matched_sub_params_shape.shape[partition_dim]
302
+ if not isinstance(sub_dim_sizes, tuple):
303
+ sub_dim_sizes = (sub_dim_sizes, )
304
+
305
+ partition_shape = [sum(d) if isinstance(d, tuple) else d for d in matched_sub_params_shape.shape]
306
+ partition_shape = [d // tp_degree if i == partition_dim else d for i, d in enumerate(partition_shape)]
307
+ slices = [s.view(partition_shape) for s in slices]
308
+
309
+ offset = 0
310
+ for sub_dim_size in sub_dim_sizes:
311
+ part_sub_dim_size = sub_dim_size // tp_degree
312
+ merged_chunks.append(
313
+ torch.cat([s.narrow(partition_dim, offset, part_sub_dim_size) for s in slices], dim=partition_dim))
314
+ offset += part_sub_dim_size
315
+ param = torch.cat(merged_chunks, dim=partition_dim)
316
+ ckpt_dict[SUB_PARAM_SHAPE] = matched_sub_params_shape
317
+ else:
318
+ cat_dim = 1 if get_matched_pattern(parameters_with_row_parallelism, name) else 0
319
+ # print(f"merge {name} with CAT DIM: {cat_dim}")
320
+ param = torch.cat(slices, dim=cat_dim)
321
+ ckpt_dict[CAT_DIM] = cat_dim
322
+
323
+ if get_matched_pattern(vocabulary_parameters, name):
324
+ #print(f"Before {param.shape=}")
325
+ # strip padding
326
+ original_vocab_size = universal_checkpoint_info['original_vocab_size']
327
+ param = param[:original_vocab_size, :]
328
+ ckpt_dict[VOCAB_TENSOR] = True
329
+ #print(f"After {param.shape=}")
330
+
331
+ #print(f"Final shape: {param.shape}")
332
+ ckpt_dict[PARAM] = param
333
+ _save_checkpoint(final_path, ckpt_dict)
334
+
335
+ return unmatched_patterns
336
+
337
+
338
+ def merge_zero3_slices(dp_degree, dir, slice_dir, name):
339
+ slice_base_path = os.path.join(slice_dir, name)
340
+ param_base_path = os.path.join(dir, name)
341
+
342
+ for state in ("fp32", "exp_avg", "exp_avg_sq"):
343
+ slices = _merge_zero_shards(slice_base_path, state, 1)
344
+ final_path = os.path.join(param_base_path, f"{state}.pt")
345
+ _save_checkpoint(final_path, slices[0])
346
+
347
+
348
+ def _do_parallel_work(do_work, work_chunks, num_workers):
349
+ results = []
350
+ if num_workers > 1:
351
+ with ProcessPoolExecutor(max_workers=num_workers) as executor:
352
+ future_list = [executor.submit(do_work, work) for work in work_chunks]
353
+ for f in tqdm.tqdm(future_list):
354
+ results.append(f.result())
355
+ else:
356
+ # No parallel pass for unit testing
357
+ # We can't create child processes in tests
358
+ for work in tqdm.tqdm(work_chunks):
359
+ results.append(do_work(work))
360
+ return results
361
+
362
+
363
+ def _extract_zero_shard_files(args, ds_checkpoint, temp_dir):
364
+ _3d_range_list = list(
365
+ itertools.product(range(ds_checkpoint.pp_degree), range(ds_checkpoint.tp_degree),
366
+ range(ds_checkpoint.dp_degree)))
367
+ #pprint(f'{_3d_range_list=}')
368
+
369
+ do_work = partial(extract_zero_shards, temp_dir, ds_checkpoint)
370
+ _do_parallel_work(do_work, _3d_range_list, args.num_extract_workers)
371
+
372
+
373
+ def _extract_zero_shard_files_stage3(args, optim_files, param_shapes, dp_degree, temp_dir):
374
+ do_work = partial(extract_zero_shards_stage3, optim_files, param_shapes, dp_degree, temp_dir)
375
+ _do_parallel_work(do_work, list(range(dp_degree)), args.num_extract_workers)
376
+
377
+
378
+ def _merge_tp_slice_files(args, ds_checkpoint, slice_shapes, temp_dir):
379
+ zero_output_folder = os.path.join(args.output_folder, "zero")
380
+ do_work = partial(merge_tp_slices, ds_checkpoint, zero_output_folder, temp_dir, ds_checkpoint.tp_degree)
381
+ unmatched_patterns_lists = _do_parallel_work(do_work, list(slice_shapes.items()), args.num_merge_workers)
382
+
383
+ # verify that all patterns were used
384
+ # if a pattern was not used by any of the workers, then it was not used at all -> assert/alert
385
+ sets = [set(lst) for lst in unmatched_patterns_lists]
386
+ unmatched_patterns = list(set.intersection(*sets))
387
+ if args.strict:
388
+ assert not unmatched_patterns, f'Unused patterns={unmatched_patterns} while merging tp slices'
389
+ elif unmatched_patterns:
390
+ print(f'Warning: Unused patterns={unmatched_patterns} while merging tp slices')
391
+
392
+
393
+ def _merge_zero3_slice_files(args, param_shapes, dp_degree, temp_dir):
394
+ zero_output_folder = os.path.join(args.output_folder, "zero")
395
+ do_work = partial(merge_zero3_slices, dp_degree, zero_output_folder, temp_dir)
396
+ _do_parallel_work(do_work, param_shapes.keys(), args.num_merge_workers)
397
+
398
+
399
+ def _zero_partitioned_param_info(unpartitioned_numel, world_size):
400
+ remainder = unpartitioned_numel % world_size
401
+ padding_numel = (world_size - remainder) if remainder else 0
402
+ partitioned_numel = math.ceil(unpartitioned_numel / world_size)
403
+ return partitioned_numel, padding_numel
404
+
405
+
406
+ def _parse_model_states_stage3(files):
407
+ return torch.load(files[0], map_location=torch.device('cpu'), weights_only=False)[PARAM_SHAPES]
408
+
409
+
410
+ def _save_optimizer_state(args, ds_checkpoint):
411
+ sharded_states = [BASE_OPTIMIZER_STATE, PARAM_SLICE_MAPPINGS, SINGLE_PARTITION_OF_FP32_GROUPS]
412
+ sd = ds_checkpoint.get_zero_checkpoint_state(pp_index=0, tp_index=0, dp_index=0)
413
+
414
+ optim_sd = sd[OPTIMIZER_STATE_DICT]
415
+ output_sd = {k: v for k, v in optim_sd.items() if k not in sharded_states}
416
+ output_sd[PARAM_GROUPS] = optim_sd[BASE_OPTIMIZER_STATE][PARAM_GROUPS]
417
+ zero_output_folder = os.path.join(args.output_folder, "zero")
418
+ output_file_path = os.path.join(zero_output_folder, f"optimizer_state.pt")
419
+ _save_checkpoint(output_file_path, output_sd)
420
+
421
+
422
+ def _save_optimizer_state_stage3(args, optim_files):
423
+ sd = torch.load(optim_files[0], map_location=torch.device('cpu'), weights_only=False)
424
+ output_sd = sd[OPTIMIZER_STATE_DICT]
425
+ output_sd[PARAM_GROUPS] = output_sd[OPTIMIZER_STATE_DICT][PARAM_GROUPS]
426
+ zero_output_folder = os.path.join(args.output_folder, "zero")
427
+ output_file_path = os.path.join(zero_output_folder, f"optimizer_state.pt")
428
+ _save_checkpoint(output_file_path, output_sd)
429
+
430
+
431
+ def _get_optim_files(checkpoint_dir):
432
+ return _get_checkpoint_files(checkpoint_dir, "*_optim_states.pt")
433
+
434
+
435
+ def _get_model_state_files(checkpoint_dir):
436
+ return _get_checkpoint_files(checkpoint_dir, "*_model_states.pt")
437
+
438
+
439
+ def _get_checkpoint_files(checkpoint_dir, glob_pattern):
440
+ ckpt_files = sorted(glob.glob(os.path.join(checkpoint_dir, glob_pattern)), key=natural_keys)
441
+
442
+ if len(ckpt_files) == 0:
443
+ raise FileNotFoundError(f"can't find {glob_pattern} files in directory '{checkpoint_dir}'")
444
+
445
+ return ckpt_files
446
+
447
+
448
+ def _get_zero_stage(optim_files):
449
+ state_dict = torch.load(optim_files[0], map_location=torch.device('cpu'), weights_only=False)
450
+ optimizer_state = state_dict[OPTIMIZER_STATE_DICT]
451
+ zero_stage = optimizer_state.get(ZERO_STAGE, 1)
452
+ return zero_stage
453
+
454
+
455
+ def _inject_missing_state(ds_checkpoint):
456
+ if UNIVERSAL_CHECKPOINT_INFO not in ds_checkpoint.global_state:
457
+ sd = torch.load(ds_checkpoint.mp_rank_files[0], map_location=torch.device('cpu'), weights_only=False)
458
+ if UNIVERSAL_CHECKPOINT_INFO not in sd:
459
+ ds_checkpoint.global_state[UNIVERSAL_CHECKPOINT_INFO] = {}
460
+ ds_checkpoint.global_state[UNIVERSAL_CHECKPOINT_INFO][
461
+ UNIVERSAL_CHECKPOINT_VERSION_KEY] = UNIVERSAL_CHECKPOINT_VERSION_VALUE
462
+
463
+
464
+ def _check_for_required_state(ds_checkpoint):
465
+ universal_checkpoint_info = ds_checkpoint.get_checkpoint_info(UNIVERSAL_CHECKPOINT_INFO)
466
+ assert universal_checkpoint_info is not None, f'Required {UNIVERSAL_CHECKPOINT_INFO} state is missing in checkpoint. Verify that client creates this state.'
467
+
468
+
469
+ def main(args):
470
+ print(f'Convert DeepSpeed Checkpoint to Universal Checkpoint')
471
+
472
+ print(f'Converting DeepSpeed checkpoint in {args.input_folder} to Universal checkpoint in {args.output_folder}')
473
+
474
+ optim_files = _get_optim_files(args.input_folder)
475
+ zero_stage = _get_zero_stage(optim_files)
476
+
477
+ if zero_stage <= 2:
478
+ ds_checkpoint = DeepSpeedCheckpoint(args.input_folder)
479
+ if args.inject_missing_state:
480
+ _inject_missing_state(ds_checkpoint)
481
+ else:
482
+ _check_for_required_state(ds_checkpoint)
483
+
484
+ iteration = ds_checkpoint.get_iteration()
485
+ #_create_latest_file(args.output_folder, iteration)
486
+ checkpoint_paths = _create_checkpoint_paths(args.output_folder, iteration, ds_checkpoint.tp_degree,
487
+ ds_checkpoint.pp_degree)
488
+
489
+ slice_shapes = []
490
+ for mp_rank_file in ds_checkpoint.mp_rank_files:
491
+ mp_sd = torch.load(mp_rank_file, map_location=torch.device('cpu'), weights_only=False)
492
+ slice_shapes += mp_sd[PARAM_SHAPES]
493
+
494
+ # fix back to normal flat dict, merge duplicates for tp>1
495
+ slice_shapes = dict((k, v) for d in slice_shapes for k, v in d.items())
496
+ temp_dir = os.path.join(args.output_folder, 'tmp')
497
+
498
+ print('*** 1. Extracting ZeRO fragments')
499
+ _extract_zero_shard_files(args, ds_checkpoint, temp_dir)
500
+
501
+ print('*** 2. Merging slices .....')
502
+ _merge_tp_slice_files(args, ds_checkpoint, slice_shapes, temp_dir)
503
+
504
+ print('*** 3. Saving common optimizer states')
505
+ _save_optimizer_state(args, ds_checkpoint)
506
+
507
+ if not args.keep_temp_folder:
508
+ shutil.rmtree(temp_dir, ignore_errors=True)
509
+
510
+ # Copy mp* files into output folder
511
+ for f in glob.glob(os.path.join(args.input_folder, 'mp*')):
512
+ shutil.copy2(f, args.output_folder)
513
+
514
+ else:
515
+ model_files = _get_model_state_files(args.input_folder)
516
+ param_shapes = _parse_model_states_stage3(model_files)
517
+ param_shapes = {k: v for d in param_shapes for k, v in d.items()}
518
+ dp_degree = len(model_files)
519
+
520
+ temp_dir = os.path.join(args.output_folder, 'tmp')
521
+
522
+ print('*** 1. Extracting ZeRO fragments')
523
+ _extract_zero_shard_files_stage3(args, optim_files, param_shapes, dp_degree, temp_dir)
524
+
525
+ print('*** 2. Merging slices .....')
526
+ _merge_zero3_slice_files(args, param_shapes, dp_degree, temp_dir)
527
+
528
+ print('*** 3. Saving common optimizer states')
529
+ _save_optimizer_state_stage3(args, optim_files)
530
+
531
+ if not args.keep_temp_folder:
532
+ shutil.rmtree(temp_dir, ignore_errors=True)
533
+
534
+ # Copy *model_states files into output folder
535
+ for f in glob.glob(os.path.join(args.input_folder, '*model_states.pt')):
536
+ shutil.copy2(f, args.output_folder)
537
+
538
+ # Update latest to output folder
539
+ checkpoint_root_folder, step_folder = os.path.split(args.output_folder)
540
+ latest_file = os.path.join(checkpoint_root_folder, 'latest_universal')
541
+ with open(latest_file, "w") as f:
542
+ f.write(step_folder)
543
+
544
+ print('*** Done!')
545
+
546
+
547
+ if __name__ == "__main__":
548
+ args = parse_arguments()
549
+ main(args)
lib/python3.12/site-packages/deepspeed/checkpoint/reshape_3d_utils.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ from .reshape_utils import (get_files, get_files_with_prefix, partition_data, get_zero_files)
7
+
8
+ from .constants import (MODEL_FILE_PREFIX, LAYER_FILE_PREFIX)
9
+
10
+ from .reshape_meg_2d import (reshape_meg_2d_parallel, meg_2d_parallel_map)
11
+
12
+ PP_DIM = 'PP'
13
+ TP_DIM = 'TP'
14
+ DP_DIM = 'DP'
15
+
16
+
17
+ class model_3d_desc(object):
18
+
19
+ def __init__(self, pp_degree=1, tp_degree=1, dp_degree=1):
20
+ self.pp_degree = pp_degree
21
+ self.tp_degree = tp_degree
22
+ self.dp_degree = dp_degree
23
+
24
+ def reshape(self, target_3d_desc, verbose=False):
25
+ valid_reshape, reshape_errors = self.can_reshape(target_3d_desc)
26
+ assert valid_reshape, ','.join(reshape_errors)
27
+ tgt_2d_map = reshape_meg_2d_parallel(old_pp_degree=self.pp_degree,
28
+ old_tp_degree=self.tp_degree,
29
+ new_pp_degree=target_3d_desc.pp_degree,
30
+ new_tp_degree=target_3d_desc.tp_degree,
31
+ verbose=verbose)
32
+
33
+ flat_3d_map = flatten_dp_dimension(meg_2d_map=tgt_2d_map,
34
+ src_2d_size=self.pp_degree * self.tp_degree,
35
+ dp_degree=self.dp_degree)
36
+
37
+ return unflatten_dp_dimension(meg_2d_map=flat_3d_map, dp_degree=target_3d_desc.dp_degree)
38
+
39
+ def get_desc(self):
40
+ return f'{PP_DIM},{TP_DIM},{DP_DIM} = ({self.pp_degree}, {self.tp_degree}, {self.dp_degree})'
41
+
42
+ def world_size(self):
43
+ return self.pp_degree * self.tp_degree * self.dp_degree
44
+
45
+ def is_valid(self, pp_index, tp_index, dp_index):
46
+ err_msg = []
47
+ valid = True
48
+ for index, degree, dim_name in [(pp_index, self.pp_degree, PP_DIM), (tp_index, self.tp_degree, TP_DIM),
49
+ (dp_index, self.dp_degree, DP_DIM)]:
50
+ if index >= degree:
51
+ valid = False
52
+ err_msg.append(f'{dim_name} indexing error: index {index} >= degree {degree}')
53
+
54
+ return valid, err_msg
55
+
56
+ def can_reshape(self, target_3d_desc):
57
+ err_msg = []
58
+ if target_3d_desc.pp_degree > self.pp_degree:
59
+ err_msg.append(
60
+ f'Expansion reshape not supported - {PP_DIM}: {self.pp_degree} ---> {target_3d_desc.pp_degree}')
61
+
62
+ if target_3d_desc.tp_degree > self.tp_degree:
63
+ err_msg.append(
64
+ f'Expansion reshape not supported - {TP_DIM}: {self.tp_degree} ---> {target_3d_desc.tp_degree}')
65
+
66
+ if target_3d_desc.dp_degree > self.dp_degree:
67
+ err_msg.append(
68
+ f'Expansion reshape not supported - {DP_DIM}: {self.dp_degree} ---> {target_3d_desc.dp_degree}')
69
+
70
+ return len(err_msg) == 0, err_msg
71
+
72
+
73
+ def get_model_3d_descriptor(dir):
74
+ file_list = get_files(dir)
75
+ zero_file_list = get_zero_files(dir)
76
+ num_pp0_files = len(get_files_with_prefix(file_list, f'{LAYER_FILE_PREFIX}01'))
77
+ if num_pp0_files > 0:
78
+ tp_degree = num_pp0_files
79
+ pp_degree = len(get_files_with_prefix(file_list, MODEL_FILE_PREFIX)) // tp_degree
80
+ dp_degree = max(1, len(zero_file_list) // (pp_degree * tp_degree))
81
+ else:
82
+ tp_degree = len(get_files_with_prefix(file_list, MODEL_FILE_PREFIX))
83
+ dp_degree = max(1, len(zero_file_list) // tp_degree)
84
+ pp_degree = 1
85
+
86
+ return model_3d_desc(pp_degree, tp_degree, dp_degree)
87
+
88
+
89
+ def flatten_dp_dimension(meg_2d_map, src_2d_size, dp_degree):
90
+ new_meg_2d_map = meg_2d_parallel_map(meg_2d_map.pp_degree, meg_2d_map.tp_degree)
91
+ for pp_index in range(meg_2d_map.pp_degree):
92
+ for tp_index in range(meg_2d_map.tp_degree):
93
+ dp0_indices = meg_2d_map.get_data(pp_index, tp_index)
94
+ for idx in dp0_indices:
95
+ dpX_indices = [idx + (i * src_2d_size) for i in range(dp_degree)]
96
+ new_meg_2d_map.add_data(pp_index, tp_index, dpX_indices)
97
+ return new_meg_2d_map
98
+
99
+
100
+ def unflatten_dp_dimension(meg_2d_map, dp_degree):
101
+ pp_degree = meg_2d_map.pp_degree
102
+ tp_degree = meg_2d_map.tp_degree
103
+ meg_2d_map_list = [meg_2d_parallel_map(pp_degree=pp_degree, tp_degree=tp_degree) for _ in range(dp_degree)]
104
+ for pp_index in range(pp_degree):
105
+ for tp_index in range(tp_degree):
106
+ flat_dp_indices = meg_2d_map.get_data(pp_index, tp_index)
107
+ partitioned_dp_indices = partition_data(flat_dp_indices, dp_degree)
108
+ for dp_indices, _2d_map in zip(partitioned_dp_indices, meg_2d_map_list):
109
+ _2d_map.add_data(pp_index, tp_index, dp_indices)
110
+
111
+ return meg_2d_map_list
lib/python3.12/site-packages/deepspeed/checkpoint/reshape_meg_2d.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ from .reshape_utils import partition_data
7
+
8
+
9
+ class meg_2d_parallel_map(object):
10
+
11
+ def __init__(self, pp_degree, tp_degree):
12
+ self.pp_degree = pp_degree
13
+ self.tp_degree = tp_degree
14
+ self.map = {}
15
+
16
+ def simple_init(self):
17
+ self.map = {
18
+ self._make_key(i // self.tp_degree, i % self.tp_degree): [i]
19
+ for i in range(self.pp_degree * self.tp_degree)
20
+ }
21
+
22
+ def add_data(self, pp_index, tp_index, data):
23
+ self._validate_indices(pp_index, tp_index)
24
+ assert type(data) is list
25
+
26
+ key = self._make_key(pp_index, tp_index)
27
+ if not key in self.map.keys():
28
+ self.map[key] = []
29
+ self.map[key] += data
30
+
31
+ def get_data(self, pp_index=None, tp_index=None):
32
+ self._validate_indices(pp_index, tp_index)
33
+ pp_indices = list(range(self.pp_degree)) if pp_index is None else [pp_index]
34
+ tp_indices = list(range(self.tp_degree)) if tp_index is None else [tp_index]
35
+
36
+ result = []
37
+ for i in pp_indices:
38
+ for j in tp_indices:
39
+ result += self.map[self._make_key(i, j)]
40
+
41
+ return result
42
+
43
+ def print_data(self, tag):
44
+ print(f'{tag}')
45
+ for key, value in self.map.items():
46
+ print(f'{key} = {value}')
47
+
48
+ def _validate_indices(self, pp_index, tp_index):
49
+ assert pp_index is None or pp_index < self.pp_degree
50
+ assert tp_index is None or tp_index < self.tp_degree
51
+
52
+ def _make_key(self, i, j):
53
+ return f'{i},{j}'
54
+
55
+
56
+ def _reshape_tp_dimension(old_2d_map, new_tp_degree):
57
+ old_pp_degree = old_2d_map.pp_degree
58
+ new_2d_map = meg_2d_parallel_map(old_pp_degree, new_tp_degree)
59
+ for i in range(old_pp_degree):
60
+ ranks_for_pp_index = old_2d_map.get_data(pp_index=i, tp_index=None)
61
+ split_ranks = partition_data(ranks_for_pp_index, new_tp_degree)
62
+ for j in range(new_tp_degree):
63
+ new_2d_map.add_data(i, j, split_ranks[j])
64
+
65
+ return new_2d_map
66
+
67
+
68
+ def _reshape_pp_dimension(old_2d_map, new_pp_degree):
69
+ old_tp_degree = old_2d_map.tp_degree
70
+ new_2d_map = meg_2d_parallel_map(new_pp_degree, old_tp_degree)
71
+ for i in range(old_tp_degree):
72
+ ranks_for_tp_index = old_2d_map.get_data(pp_index=None, tp_index=i)
73
+ split_ranks = partition_data(ranks_for_tp_index, new_pp_degree)
74
+ for j in range(new_pp_degree):
75
+ new_2d_map.add_data(j, i, split_ranks[j])
76
+
77
+ return new_2d_map
78
+
79
+
80
+ def reshape_meg_2d_parallel(old_pp_degree, old_tp_degree, new_pp_degree, new_tp_degree, verbose=False):
81
+ assert new_pp_degree <= old_pp_degree
82
+ assert new_tp_degree <= old_tp_degree
83
+
84
+ old_2d_map = meg_2d_parallel_map(old_pp_degree, old_tp_degree)
85
+ old_2d_map.simple_init()
86
+ if verbose:
87
+ old_2d_map.print_data(f'original_2d_map:')
88
+
89
+ if old_tp_degree != new_tp_degree:
90
+ new_tp_map = _reshape_tp_dimension(old_2d_map, new_tp_degree)
91
+ else:
92
+ new_tp_map = old_2d_map
93
+ if verbose:
94
+ new_tp_map.print_data(f'after_tp_reshape:')
95
+
96
+ if old_pp_degree != new_pp_degree:
97
+ final_map = _reshape_pp_dimension(new_tp_map, new_pp_degree)
98
+ else:
99
+ final_map = new_tp_map
100
+
101
+ if verbose:
102
+ final_map.print_data(f'final_2d_map:')
103
+
104
+ return final_map
105
+
106
+
107
+ def get_mpu_ranks(tp_size=1, pp_size=1, dp_size=1, virtual_pp_size=None):
108
+ """
109
+ Initialize model data parallel groups.
110
+
111
+ Arguments:
112
+ tp_size: number of GPUs used to parallelize model tensor.
113
+ pp_size: number of GPUs used to parallelize model pipeline.
114
+ dp_size: number of GPUs used to parallelize model data.
115
+
116
+ Let's say we have a total of 16 GPUs denoted by g0 ... g15 and we
117
+ use 2 GPUs to parallelize the model tensor, and 4 GPUs to parallelize
118
+ the model pipeline. The present function will
119
+ create 8 tensor model-parallel groups, 4 pipeline model-parallel groups
120
+ and 8 data-parallel groups as:
121
+ 8 data_parallel groups:
122
+ [g0, g2], [g1, g3], [g4, g6], [g5, g7], [g8, g10], [g9, g11], [g12, g14], [g13, g15]
123
+ 8 tensor model-parallel groups:
124
+ [g0, g1], [g2, g3], [g4, g5], [g6, g7], [g8, g9], [g10, g11], [g12, g13], [g14, g15]
125
+ 4 pipeline model-parallel groups:
126
+ [g0, g4, g8, g12], [g1, g5, g9, g13], [g2, g6, g10, g14], [g3, g7, g11, g15]
127
+ Note that for efficiency, the caller should make sure adjacent ranks
128
+ are on the same DGX box. For example if we are using 2 DGX-1 boxes
129
+ with a total of 16 GPUs, rank 0 to 7 belong to the first box and
130
+ ranks 8 to 15 belong to the second box.
131
+ """
132
+
133
+ world_size = tp_size * pp_size * dp_size
134
+
135
+ print(f"\n\n*** tp={tp_size}, pp={pp_size}, dp={dp_size}, world={world_size}")
136
+
137
+ tensor_model_parallel_size = min(tp_size, world_size)
138
+ pipeline_model_parallel_size = min(pp_size, world_size)
139
+ data_parallel_size = world_size // (tensor_model_parallel_size * pipeline_model_parallel_size)
140
+
141
+ num_tensor_model_parallel_groups = world_size // tensor_model_parallel_size
142
+ num_pipeline_model_parallel_groups = world_size // pipeline_model_parallel_size
143
+ num_data_parallel_groups = world_size // data_parallel_size
144
+
145
+ # Build the data-parallel groups.
146
+ all_dp_group_ranks = []
147
+ for i in range(pipeline_model_parallel_size):
148
+ start_rank = i * num_pipeline_model_parallel_groups
149
+ end_rank = (i + 1) * num_pipeline_model_parallel_groups
150
+ for j in range(tensor_model_parallel_size):
151
+ ranks = range(start_rank + j, end_rank, tensor_model_parallel_size)
152
+ all_dp_group_ranks.append(list(ranks))
153
+
154
+ print("DP", all_dp_group_ranks)
155
+
156
+ # Build the model-parallel groups.
157
+ all_pp_group_ranks = []
158
+ for i in range(data_parallel_size):
159
+ ranks = [data_parallel_group_ranks[i] for data_parallel_group_ranks in all_dp_group_ranks]
160
+ all_pp_group_ranks.append(list(ranks))
161
+
162
+ print(f"PP", all_pp_group_ranks)
163
+
164
+ # Build the tensor model-parallel groups.
165
+ all_tp_group_ranks = []
166
+ for i in range(num_tensor_model_parallel_groups):
167
+ ranks = range(i * tensor_model_parallel_size, (i + 1) * tensor_model_parallel_size)
168
+ all_tp_group_ranks.append(list(ranks))
169
+
170
+ print(f"TP", all_tp_group_ranks)
171
+
172
+ return all_tp_group_ranks, all_pp_group_ranks, all_dp_group_ranks
173
+
174
+ # # Build the pipeline model-parallel groups and embedding groups
175
+ # # (first and last rank in each pipeline model-parallel group).
176
+ # for i in range(num_pipeline_model_parallel_groups):
177
+ # ranks = range(i, world_size,
178
+ # num_pipeline_model_parallel_groups)
179
+ # print(f"EMB{i}", list(ranks))
180
+
181
+
182
+ def reshape(src, tgt):
183
+ """
184
+ reshape([tp_size_src, pp_size_src, dp_size_src],
185
+ [tp_size_tgt, pp_size_tgt, dp_size_tgt])
186
+ """
187
+
188
+ print(f"\n\n*** Reshaping: {src} => {tgt}")
189
+
190
+ tp_size_src, pp_size_src, dp_size_src = src
191
+ tp_size_tgt, pp_size_tgt, dp_size_tgt = tgt
192
+
193
+ tp_ranks1, pp_ranks1, dp_ranks1 = get_mpu_ranks(tp_size=tp_size_src, pp_size=pp_size_src, dp_size=dp_size_src)
194
+ tp_ranks2, pp_ranks2, dp_ranks2 = get_mpu_ranks(tp_size=tp_size_tgt, pp_size=pp_size_src, dp_size=dp_size_src)
195
+ tp_ranks3, pp_ranks3, dp_ranks3 = get_mpu_ranks(tp_size=tp_size_tgt, pp_size=pp_size_tgt, dp_size=dp_size_src)
196
+
197
+ # handle tp contraction first
198
+ print("\n*** TP contraction:")
199
+
200
+ for i, r in enumerate(tp_ranks1):
201
+ print(f'{tp_ranks1[i]} => {tp_ranks2[i]}')
202
+
203
+ # handle pp contraction next
204
+
205
+ print("\n*** PP contraction:")
206
+
207
+ for i, r in enumerate(pp_ranks1):
208
+ print(f'{pp_ranks2[i]} => {pp_ranks3[i]}')
209
+
210
+
211
+ # easy
212
+ #reshape([2,2,1],[1,1,1])
213
+
214
+ # probably need more logic to suggest how to pack
215
+ #reshape([4,4,1],[2,2,1])
216
+
217
+ #reshape([2,4,2], [8,32,1])
218
+
219
+ # get_mpu_ranks(2,2,2)
220
+ # get_mpu_ranks(4,2,1)
221
+ # get_mpu_ranks(2,4,1)
222
+ # get_mpu_ranks(1,1,8)
lib/python3.12/site-packages/deepspeed/checkpoint/reshape_utils.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Microsoft Corporation.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # DeepSpeed Team
5
+
6
+ import os
7
+ import re
8
+ import torch
9
+ from collections import OrderedDict
10
+ from .constants import (ZERO_FILE_PREFIX, FP16_ZERO_FILE_PREFIX, BF16_ZERO_FILE_PREFIX, MODEL_FILE_PREFIX)
11
+
12
+
13
+ def basic_folder_validation(dir):
14
+ assert os.path.exists(dir), f'{dir} path does not exist'
15
+ assert os.path.isdir(dir), f'{dir} is not a folder'
16
+
17
+
18
+ def get_files_with_prefix(all_files, prefix):
19
+ file_list = []
20
+ for file_path in all_files:
21
+ _, fname = os.path.split(file_path)
22
+ if fname.startswith(prefix):
23
+ file_list.append(file_path)
24
+
25
+ return sorted(file_list)
26
+
27
+
28
+ def validate_files(file_list):
29
+ for file in file_list:
30
+ if not os.path.isfile(file):
31
+ print(f'Error: {file} is not existent')
32
+
33
+
34
+ def get_files(dir):
35
+ file_list = []
36
+ for root, _, files in os.walk(dir):
37
+ for file in files:
38
+ file_list.append(os.path.join(root, file))
39
+ return file_list
40
+
41
+
42
+ def sort_zero_files(files, prefix):
43
+ pattern = f"{prefix}([0-9]+)_{MODEL_FILE_PREFIX}([0-9]+)"
44
+ rank_pairs = []
45
+ for f in files:
46
+ m = re.search(pattern, f)
47
+ if m:
48
+ dp_rank = int(m.group(1))
49
+ mp_rank = int(m.group(2))
50
+ rank_pairs.append((dp_rank, mp_rank, f))
51
+ else:
52
+ raise ValueError(f"Cannot parse dp_rank and mp_rank from {f}")
53
+
54
+ sorted_files = sorted(rank_pairs, key=lambda x: (x[0], x[1]))
55
+ return [f for _, _, f in sorted_files]
56
+
57
+
58
+ def get_zero_files(dir):
59
+ file_list = get_files(dir)
60
+ for prefix in [ZERO_FILE_PREFIX, FP16_ZERO_FILE_PREFIX, BF16_ZERO_FILE_PREFIX]:
61
+ zero_files = get_files_with_prefix(file_list, prefix)
62
+ if len(zero_files) > 0:
63
+ return sort_zero_files(zero_files, prefix)
64
+
65
+ return []
66
+
67
+
68
+ def partition_data(data_list, num_partitions):
69
+ num_elems = len(data_list)
70
+ assert num_elems % num_partitions == 0
71
+ partition_size = num_elems // num_partitions
72
+ partitions_list = [data_list[i:i + partition_size] for i in range(0, num_elems, partition_size)]
73
+ return partitions_list
74
+
75
+
76
+ def _key_list_to_string(key_list):
77
+ return '.'.join(key_list)
78
+
79
+
80
+ def merge_state_dict(dict_a, dict_b, key_list):
81
+ merged_dict = type(dict_a)({})
82
+
83
+ for key, value in dict_b.items():
84
+ if key in dict_a.keys():
85
+ merged_dict[key] = merge_state(dict_a[key], dict_b[key], [str(key)])
86
+ else:
87
+ merged_dict[key] = value
88
+
89
+ return merged_dict
90
+
91
+
92
+ def merge_state_list(list_a, list_b, key_list):
93
+ if len(list_a) != len(list_b):
94
+ print(f'{_key_list_to_string(key_list)}')
95
+ raise ValueError(f'Cannot merge lists of different lengths, a = {len(list_a)} b = {len(list_b)}')
96
+
97
+ return [merge_state(a, b, key_list) for a, b in zip(list_a, list_b)]
98
+
99
+
100
+ def merge_state(state_a, state_b, key_list=[]):
101
+ if type(state_a) != type(state_b):
102
+ key_list_string = _key_list_to_string(key_list)
103
+ print(f'key_list = {key_list_string}')
104
+ raise ValueError(f'Cannot merge two states of types {type(state_a)} and type {type(state_b)}')
105
+
106
+ if type(state_a) in (dict, OrderedDict):
107
+ return merge_state_dict(state_a, state_b, key_list)
108
+ elif type(state_a) in (list, tuple):
109
+ return type(state_a)(merge_state_list(state_a, state_b, key_list))
110
+ elif torch.is_tensor(state_a):
111
+ return torch.cat([state_a, state_b], 0)
112
+ else:
113
+ return state_a