Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/deepspeed/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/__pycache__/constants.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/__pycache__/env_report.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/__pycache__/git_version_info.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/__pycache__/git_version_info_installed.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/__init__.py +6 -0
- lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/autotuner.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/config.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/constants.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/scheduler.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/autotuner.py +1113 -0
- lib/python3.12/site-packages/deepspeed/autotuning/config.py +98 -0
- lib/python3.12/site-packages/deepspeed/autotuning/config_templates/template_zero0.json +5 -0
- lib/python3.12/site-packages/deepspeed/autotuning/config_templates/template_zero1.json +7 -0
- lib/python3.12/site-packages/deepspeed/autotuning/config_templates/template_zero2.json +11 -0
- lib/python3.12/site-packages/deepspeed/autotuning/config_templates/template_zero3.json +17 -0
- lib/python3.12/site-packages/deepspeed/autotuning/constants.py +185 -0
- lib/python3.12/site-packages/deepspeed/autotuning/scheduler.py +432 -0
- lib/python3.12/site-packages/deepspeed/autotuning/tuner/__init__.py +8 -0
- lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/base_tuner.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/cost_model.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/index_based_tuner.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/model_based_tuner.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/tuner/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/autotuning/tuner/base_tuner.py +72 -0
- lib/python3.12/site-packages/deepspeed/autotuning/tuner/cost_model.py +66 -0
- lib/python3.12/site-packages/deepspeed/autotuning/tuner/index_based_tuner.py +40 -0
- lib/python3.12/site-packages/deepspeed/autotuning/tuner/model_based_tuner.py +157 -0
- lib/python3.12/site-packages/deepspeed/autotuning/tuner/utils.py +86 -0
- lib/python3.12/site-packages/deepspeed/autotuning/utils.py +459 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/__init__.py +20 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/constants.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/deepspeed_checkpoint.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/ds_to_universal.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/reshape_3d_utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/reshape_meg_2d.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/reshape_utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/universal_checkpoint.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/__pycache__/zero_checkpoint.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/constants.py +87 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/deepspeed_checkpoint.py +307 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/ds_to_universal.py +549 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/reshape_3d_utils.py +111 -0
- lib/python3.12/site-packages/deepspeed/checkpoint/reshape_meg_2d.py +222 -0
- 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
|