1369 lines
58 KiB
Python
1369 lines
58 KiB
Python
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from __future__ import annotations
|
|
|
|
import paddle
|
|
from paddle.distributed.launch.context import Context
|
|
from paddle.distributed.utils import launch_utils
|
|
|
|
ctx = None
|
|
|
|
|
|
def launch() -> None:
|
|
"""
|
|
Paddle distribution training entry ``python -m paddle.distributed.launch``.
|
|
|
|
Usage:
|
|
.. code-block:: bash
|
|
:name: code-block-bash1
|
|
|
|
python -m paddle.distributed.launch [-h] [--master MASTER] [--rank RANK]
|
|
[--log_level LOG_LEVEL] [--nnodes NNODES]
|
|
[--nproc_per_node NPROC_PER_NODE] [--log_dir LOG_DIR]
|
|
[--run_mode RUN_MODE] [--job_id JOB_ID] [--devices DEVICES]
|
|
[--host HOST] [--servers SERVERS] [--trainers TRAINERS]
|
|
[--trainer_num TRAINER_NUM] [--server_num SERVER_NUM]
|
|
[--gloo_port GLOO_PORT] [--with_gloo WITH_GLOO]
|
|
[--max_restart MAX_RESTART] [--elastic_level ELASTIC_LEVEL]
|
|
[--elastic_timeout ELASTIC_TIMEOUT]
|
|
training_script ...
|
|
|
|
|
|
Base Parameters:
|
|
- ``--master``: The master/rendezvous server, support ``http://`` and ``etcd://``, default with ``http://``. e.g., ``--master=127.0.0.1:8080``. Default ``--master=None``.
|
|
|
|
- ``--rank``: The rank of the node, can be auto assigned by master. Default ``--rank=-1``.
|
|
|
|
- ``--log_level``: The log level to set for logging.setLevel which can be CRITICAL/ERROR/WARNING/INFO/DEBUG/NOTSET, case insensitive. Default ``--log_level=INFO``.
|
|
|
|
- ``--nnodes``: The number of nodes for a distributed job, it can be a range in elastic mode, e.g., ``--nnodes=2:3``. Default ``--nnodes=1``.
|
|
|
|
- ``--nproc_per_node``: The number of processes to launch on a node. In gpu training, it should be less or equal to the gpus number of you system. e.g., ``--nproc_per_node=8``
|
|
|
|
- ``--log_dir``: The path for each process's log. e.g., ``--log_dir=output_dir``. Default ``--log_dir=log``.
|
|
|
|
- ``--run_mode``: The run mode of job, can be:collective/ps/ps-heter/rpc. e.g., ``--run_mode=ps``. Default ``--run_mode=collective``.
|
|
|
|
- ``--job_id``: The job unique id, it affects the log files' name. e.g., ``--job_id=job1``. Default ``--job_id=default``.
|
|
|
|
- ``--devices``: The selected accelerate devices on nodes, can be gpu/xpu etc.. e.g., ``--devices=0,1,2,3`` will launch four training processes each bound to one device.
|
|
|
|
- ``training_script``: The full path to the single GPU training program/script to be launched in parallel, followed by all the arguments for the training script. e.g., ``training.py``
|
|
|
|
- ``training_script_args``: The args of training_script. e.g., ``--lr=0.1``
|
|
|
|
Collective Parameters:
|
|
- ``--ips``: [DEPRECATED] Paddle cluster nodes ips, e.g., ``--ips=192.168.0.16,192.168.0.17``. Default ``--ips=127.0.0.1``.
|
|
|
|
Parameter-Server Parameters:
|
|
- ``--servers``: User defined servers ip:port, e.g., ``--servers="192.168.0.16:6170,192.168.0.17:6170"``
|
|
|
|
- ``--trainers``: User defined trainers ip:port, e.g., ``--trainers="192.168.0.16:6171,192.168.0.16:6172,192.168.0.17:6171,192.168.0.17:6172"``
|
|
|
|
- ``--workers``: [DEPRECATED] The same as trainers.
|
|
|
|
- ``--trainer_num``: Number of trainers on each node, can be 0.
|
|
|
|
- ``--worker_num``: [DEPRECATED] The same as trainer_num.
|
|
|
|
- ``--server_num``: Number of servers on each node, can be 0.
|
|
|
|
- ``--heter_workers``: User defined heter workers ip1:port1;ip2:port2, e.g., ``--heter_workers="192.168.0.16:6172;192.168.0.17:6172"``
|
|
|
|
- ``--heter_worker_num``: Number of heter_workers in each stage (It recommend to set when in the emulated distributed environment using single node)
|
|
|
|
- ``--heter_devices``: Type of heter_device in each stage
|
|
|
|
- ``--gloo_port``: Gloo http Port. Default ``--gloo_port=6767``.
|
|
|
|
- ``--with_gloo``: Using gloo or not. Default ``--with_gloo=0``.
|
|
|
|
Elastic Parameters:
|
|
- ``--max_restart``: The maximum restart times for an elastic job. Default ``--max_restart=3``.
|
|
|
|
- ``--elastic_level``: The elastic level: -1: disable, 0: failed exit, peers hold, 1: internal restart. Default ``--elastic_level=-1``.
|
|
|
|
- ``--elastic_timeout``: Seconds to wait before elastic job begin to train. Default ``--elastic_timeout=30``.
|
|
|
|
IPU Parameters:
|
|
IPU distributed launch only requires and allows three arguments ``--devices``, ``training_script`` and ``training_script_args``.
|
|
The ``--devices`` is the number of IPU devices. e.g., ``--devices=4`` will launch the training program with four IPU devices.
|
|
The ``training_script`` is only allowed to set as ``ipu``.
|
|
The ``training_script_args`` includes arguments required by IPU distributed launch and illustrated as below.
|
|
``Examples 10`` has provided a example of paddle.distributed.launch with IPUs.
|
|
|
|
- ``--hosts``: The hosts for IPU distributed training. Each host is able to include multiple processes.
|
|
|
|
- ``--nproc_per_host``: The number of processes launched per host. Each process is able to include multiple replicas.
|
|
|
|
- ``--ipus_per_replica``: The number of IPUs requested per replica. Each replica is able to include multiple IPUs.
|
|
|
|
- ``--ipu_partition``: The partition name of IPU devices.
|
|
|
|
- ``--vipu_server``: The ip of the IPU device manager.
|
|
|
|
- ``training_script``: The full path to the IPU distributed training program/script to be launched in parallel. e.g., ``training.py``.
|
|
|
|
- ``training_script_args``: The args of the IPU distributed training program/script. e.g., ``--lr=0.1``.
|
|
|
|
Returns:
|
|
- ``None``
|
|
|
|
Examples 0 (master, ip/port auto detection):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash0
|
|
|
|
# For training on multi node, run the following command in one of the nodes
|
|
|
|
python -m paddle.distributed.launch --nnodes 2 train.py
|
|
|
|
# Then the following info will be print
|
|
|
|
# Copy the following command to other nodes to run.
|
|
# --------------------------------------------------------------------------------
|
|
# python -m paddle.distributed.launch --master 10.0.0.1:38714 --nnodes 2 train.py
|
|
# --------------------------------------------------------------------------------
|
|
|
|
# Follow the instruction above and paste the command in other nodes can launch a multi nodes training job.
|
|
|
|
# There are two ways to launch a job with the same command for multi nodes training
|
|
# 1) using the following command in every nodes, make sure the ip is one of the training node and the port is available on that node
|
|
# python -m paddle.distributed.launch --master 10.0.0.1:38714 --nnodes 2 train.py
|
|
# 2) using the following command in every nodes with a independent etcd service
|
|
# python -m paddle.distributed.launch --master etcd://10.0.0.1:2379 --nnodes 2 train.py
|
|
|
|
# This functionality works will for both collective and ps mode and even with other arguments.
|
|
|
|
|
|
Examples 1 (collective, single node):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash1
|
|
|
|
# For training on single node using 4 gpus.
|
|
|
|
python -m paddle.distributed.launch --devices=0,1,2,3 train.py --lr=0.01
|
|
|
|
Examples 2 (collective, multi node):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash2
|
|
|
|
# For training on multiple nodes, e.g., 192.168.0.16, 192.168.0.17
|
|
|
|
# On 192.168.0.16:
|
|
|
|
python -m paddle.distributed.launch --devices=0,1,2,3 --master=192.168.0.16:8090 train.py --lr=0.01
|
|
|
|
# On 192.168.0.17:
|
|
python -m paddle.distributed.launch --devices=0,1,2,3 --master=192.168.0.16:8090 train.py --lr=0.01
|
|
|
|
Examples 3 (ps, cpu, single node):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash3
|
|
|
|
# To simulate distributed environment using single node, e.g., 2 servers and 4 workers.
|
|
|
|
python -m paddle.distributed.launch --server_num=2 --worker_num=4 train.py --lr=0.01
|
|
|
|
Examples 4 (ps, cpu, multi node):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash4
|
|
|
|
# For training on multiple nodes, e.g., 192.168.0.16, 192.168.0.17 where each node with 1 server and 2 workers.
|
|
|
|
# On 192.168.0.16:
|
|
|
|
python -m paddle.distributed.launch --servers="192.168.0.16:6170,192.168.0.17:6170" --workers="192.168.0.16:6171,192.168.0.16:6172,192.168.0.17:6171,192.168.0.17:6172" train.py --lr=0.01
|
|
|
|
# On 192.168.0.17:
|
|
|
|
python -m paddle.distributed.launch --servers="192.168.0.16:6170,192.168.0.17:6170" --workers="192.168.0.16:6171,192.168.0.16:6172,192.168.0.17:6171,192.168.0.17:6172" train.py --lr=0.01
|
|
|
|
# Or with master, the following command run 2 server and 2 trainer on each node.
|
|
|
|
python -m paddle.distributed.launch --master 192.168.0.16:9090 --server_num=2 --trainer_num=2 --nnodes 2 train.py
|
|
|
|
|
|
Examples 5 (ps, gpu, single node):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash5
|
|
|
|
# To simulate distributed environment using single node, e.g., 2 servers and 4 workers, each worker use single gpu.
|
|
|
|
export CUDA_VISIBLE_DEVICES=0,1,2,3
|
|
python -m paddle.distributed.launch --server_num=2 --worker_num=4 train.py --lr=0.01
|
|
|
|
Examples 6 (ps, gpu, multi node):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash6
|
|
|
|
# For training on multiple nodes, e.g., 192.168.0.16, 192.168.0.17 where each node with 1 server and 2 workers.
|
|
|
|
# On 192.168.0.16:
|
|
|
|
export CUDA_VISIBLE_DEVICES=0,1
|
|
python -m paddle.distributed.launch --servers="192.168.0.16:6170,192.168.0.17:6170" --workers="192.168.0.16:6171,192.168.0.16:6172,192.168.0.17:6171,192.168.0.17:6172" train.py --lr=0.01
|
|
|
|
# On 192.168.0.17:
|
|
|
|
export CUDA_VISIBLE_DEVICES=0,1
|
|
python -m paddle.distributed.launch --servers="192.168.0.16:6170,192.168.0.17:6170" --workers="192.168.0.16:6171,192.168.0.16:6172,192.168.0.17:6171,192.168.0.17:6172" train.py --lr=0.01
|
|
|
|
Examples 7 (ps-heter, cpu + gpu, single node):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash7
|
|
|
|
# To simulate distributed environment using single node, e.g., 2 servers and 4 workers, two workers use gpu, two workers use cpu.
|
|
|
|
export CUDA_VISIBLE_DEVICES=0,1
|
|
python -m paddle.distributed.launch --server_num=2 --worker_num=2 --heter_worker_num=2 train.py --lr=0.01
|
|
|
|
Examples 8 (ps-heter, cpu + gpu, multi node):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash8
|
|
|
|
# For training on multiple nodes, e.g., 192.168.0.16, 192.168.0.17 where each node with 1 server, 1 gpu worker, 1 cpu worker.
|
|
|
|
# On 192.168.0.16:
|
|
|
|
export CUDA_VISIBLE_DEVICES=0
|
|
python -m paddle.distributed.launch --servers="192.168.0.16:6170,192.168.0.17:6170" --workers="192.168.0.16:6171,192.168.0.17:6171" --heter_workers="192.168.0.16:6172,192.168.0.17:6172" train.py --lr=0.01
|
|
|
|
# On 192.168.0.17:
|
|
|
|
export CUDA_VISIBLE_DEVICES=0
|
|
python -m paddle.distributed.launch --servers="192.168.0.16:6170,192.168.0.17:6170" --workers="192.168.0.16:6171,192.168.0.17:6171" --heter_workers="192.168.0.16:6172,192.168.0.17:6172" train.py --lr=0.01
|
|
|
|
Examples 9 (elastic):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash9
|
|
|
|
# With the following command, the job will begin to run immediately if 4 nodes are ready,
|
|
# or it will run after elastic_timeout if only 2 or 3 nodes ready
|
|
python -m paddle.distributed.launch --master etcd://10.0.0.1:2379 --nnodes 2:4 train.py
|
|
|
|
# once the number of nodes changes between 2:4 during training, the strategy holds
|
|
|
|
Examples 10 (ipu):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash10
|
|
|
|
# With the following command, the job will begin to run the distributhed program with IPUs
|
|
# Require `devices` as the number of IPUs
|
|
# Require `training_script` to be set as `ipu`
|
|
# Require `training_script_args` as the arguments of IPU distributed training instead of the arguments of the training program/script
|
|
# Please Check the `IPU Parameters` for details
|
|
python -m paddle.distributed.launch --devices 4 ipu --hosts=localhost --nproc_per_host=2 --ipus_per_replica=1 --ipu_partition=pod16 --vipu_server=127.0.0.1 train.py
|
|
|
|
Examples 11 (rpc, cpu, single node):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash11
|
|
|
|
# Training on single node with two local servers
|
|
python -m paddle.distributed.launch --master 127.0.0.1:8765 --nnodes 1 --nproc_per_node 2 --rank 0 --run_mode rpc train.py
|
|
|
|
Examples 12 (rpc, cpu, multi node):
|
|
.. code-block:: bash
|
|
:name: code-block-example-bash12
|
|
|
|
# For training on multiple nodes, e.g., 192.168.0.16, 192.168.0.17 where each node with 2 servers.
|
|
|
|
# On 192.168.0.16
|
|
|
|
python -m paddle.distributed.launch --master 192.168.0.16:8765 --nnodes 2 --nproc_per_node 2 --rank 0 --run_mode rpc train.py
|
|
|
|
# On 192.168.0.17
|
|
|
|
python -m paddle.distributed.launch --master 192.168.0.16:8765 --nnodes 2 --nproc_per_node 2 --rank 1 --run_mode rpc train.py
|
|
|
|
"""
|
|
|
|
# initialize the context to run
|
|
global ctx
|
|
ctx = Context()
|
|
|
|
if ctx.is_legacy_mode():
|
|
# legacy mode
|
|
from paddle.distributed.fleet import launch
|
|
|
|
launch.launch()
|
|
|
|
elif ctx.is_auto_tuner_mode():
|
|
import copy
|
|
import json
|
|
import logging
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
from paddle.distributed.auto_tuner.recorder import HistoryRecorder
|
|
from paddle.distributed.auto_tuner.tuner import AutoTuner
|
|
from paddle.distributed.auto_tuner.utils import (
|
|
add_overlap_performance,
|
|
find_error_from_log,
|
|
gen_new_args,
|
|
gen_new_ctx,
|
|
read_completed,
|
|
read_log,
|
|
read_step_time_log,
|
|
)
|
|
from paddle.distributed.launch import controllers
|
|
|
|
start_time = time.time()
|
|
# read user defined tuner config json
|
|
if not ctx.args.auto_tuner_json.endswith(".json"):
|
|
raise ValueError("Please use '.json' as the file name suffix.")
|
|
try:
|
|
with open(ctx.args.auto_tuner_json, "r") as f:
|
|
tuner_cfg = json.load(f)
|
|
except:
|
|
raise ValueError("Please check your auto tuner json whether valid.")
|
|
|
|
logger = logging.getLogger('auto_tuner')
|
|
logger.setLevel(logging.INFO)
|
|
auto_tuner_log_path = os.path.join(
|
|
os.path.dirname(ctx.args.auto_tuner_json),
|
|
f'{os.path.basename(ctx.args.auto_tuner_json).split(".")[0]}_auto_tuner.log',
|
|
)
|
|
handler = logging.FileHandler(auto_tuner_log_path, mode="w")
|
|
handler.setLevel(logging.INFO)
|
|
formatter = logging.Formatter(
|
|
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
)
|
|
handler.setFormatter(formatter)
|
|
logger.addHandler(handler)
|
|
|
|
# copy training script args
|
|
if ctx.args.training_script.endswith('.py'):
|
|
if os.environ.get("WITH_COVERAGE") == "ON":
|
|
entrypoint = [
|
|
sys.executable,
|
|
"-u",
|
|
"-m",
|
|
"coverage",
|
|
"run",
|
|
"--branch",
|
|
"-p",
|
|
ctx.args.training_script,
|
|
]
|
|
else:
|
|
entrypoint = [sys.executable, "-u", ctx.args.training_script]
|
|
elif ctx.args.training_script.endswith('.pyxes'):
|
|
entrypoint = [sys.executable, ctx.args.training_script]
|
|
else:
|
|
entrypoint = [ctx.args.training_script]
|
|
entrypoint.extend(ctx.args.training_script_args)
|
|
raw_args = copy.deepcopy(ctx.args.training_script_args)
|
|
|
|
# get nodes and gpus from args
|
|
if not ctx.args.devices:
|
|
gpus_per_node = 8
|
|
else:
|
|
gpus_per_node = len(ctx.args.devices.split(","))
|
|
nnodes = ctx.args.nnodes
|
|
if isinstance(nnodes, str):
|
|
nnodes = int(nnodes.split(":")[0])
|
|
else:
|
|
nnodes = int(nnodes)
|
|
tuner_cfg["nodes"] = nnodes
|
|
tuner_cfg["gpus_per_node"] = gpus_per_node
|
|
tuner_cfg["num_gpus"] = gpus_per_node * tuner_cfg["nodes"]
|
|
if not tuner_cfg.get("search_algo", None):
|
|
tuner_cfg["search_algo"] = {"name": "grid"}
|
|
mode = tuner_cfg.get("mode", None)
|
|
|
|
history_file_path = os.path.join(
|
|
os.path.dirname(ctx.args.auto_tuner_json),
|
|
f'{os.path.basename(ctx.args.auto_tuner_json).split(".")[0]}_history.csv',
|
|
)
|
|
sorted_ips = []
|
|
ip = None
|
|
if nnodes > 1:
|
|
from paddle.distributed.launch.utils.etcd_client import ETCDClient
|
|
|
|
assert ctx.args.master.startswith("etcd://")
|
|
master_ip, port = ctx.args.master.removeprefix("etcd://").split(':')
|
|
client = ETCDClient(host=master_ip, port=port)
|
|
client.delete("best_cfg")
|
|
client.delete_prefix("auto_tuner")
|
|
|
|
import socket
|
|
|
|
try:
|
|
hostname = socket.gethostname()
|
|
ip = socket.gethostbyname(socket.getfqdn(hostname))
|
|
except:
|
|
ip = '127.0.0.1'
|
|
assert ip != '127.0.0.1'
|
|
if tuner_cfg["search_algo"].get("estimated_num_gpus", None):
|
|
# get all machine ips and sort them
|
|
# to avoid etcd deleting key and adding key at the same time
|
|
time.sleep(5)
|
|
path = f"auto_tuner/ip/{ip}"
|
|
while not client.put(path, f"{ip}".encode('latin-1')):
|
|
time.sleep(1)
|
|
|
|
ips = list(client.get_prefix("auto_tuner/ip/"))
|
|
size = len(ips)
|
|
while size != nnodes:
|
|
time.sleep(1)
|
|
client.put(path, f"{ip}".encode('latin-1'))
|
|
ips = list(client.get_prefix("auto_tuner/ip/"))
|
|
size = len(ips)
|
|
sorted_ips = sorted([i[0].decode() for i in ips])
|
|
logger.info(
|
|
f"The total count of nodes is {len(sorted_ips)} and sorted ips are {sorted_ips}."
|
|
)
|
|
|
|
# get max time per task run
|
|
max_time_per_task = tuner_cfg.get("max_time_per_task", 1800)
|
|
tuner_cfg["max_time_per_task"] = max_time_per_task
|
|
ctx.max_time_per_task = max_time_per_task
|
|
|
|
# warmup
|
|
warmup_time = (
|
|
max_time_per_task
|
|
if "warmup_time" not in tuner_cfg
|
|
else tuner_cfg.get("warmup_time")
|
|
)
|
|
|
|
# max_search_time
|
|
max_search_time = tuner_cfg.get("max_search_time", None)
|
|
|
|
# buffer and memory
|
|
buffer = tuner_cfg.get("buffer", None)
|
|
max_mem_usage = tuner_cfg.get("max_mem_usage", None)
|
|
|
|
is_first_task = True
|
|
# build history recorder
|
|
recorder = HistoryRecorder(tuner_cfg)
|
|
|
|
job_id = 0
|
|
error_task_nums = 0
|
|
ctx.args.max_restart = -1
|
|
raw_ctx = copy.deepcopy(ctx)
|
|
|
|
# gbs search
|
|
if (
|
|
tuner_cfg.get('model_cfg', {}).get('global_batch_size', 'auto')
|
|
== "auto"
|
|
):
|
|
# adjust micron batch size until out of memory to get best global batch size
|
|
gbs_tuner_cfg = copy.deepcopy(tuner_cfg)
|
|
gbs_tuner_cfg["search_algo"] = "gbs"
|
|
gbs_tuner = AutoTuner(gbs_tuner_cfg)
|
|
|
|
gbs_cur_cfg = gbs_tuner.search_once()
|
|
best_gbs = None
|
|
|
|
# every task has own job id
|
|
job_id += 1
|
|
task_job_id = "gbs_tuner_" + str(job_id)
|
|
ctx.args.job_id = task_job_id
|
|
|
|
while gbs_cur_cfg:
|
|
ctx = copy.deepcopy(raw_ctx)
|
|
log_dir = "Job{}_GBSSearch/GBS{}_DP{}_MP{}_PP{}_Sharding_degree_{}_stage_{}_MBS{}_Recompute_{}_granularity_{}".format(
|
|
job_id,
|
|
gbs_cur_cfg["global_batch_size"],
|
|
gbs_cur_cfg["dp_degree"],
|
|
gbs_cur_cfg["mp_degree"],
|
|
gbs_cur_cfg["pp_degree"],
|
|
gbs_cur_cfg["sharding_degree"],
|
|
gbs_cur_cfg["sharding_stage"],
|
|
gbs_cur_cfg["micro_batch_size"],
|
|
gbs_cur_cfg["use_recompute"],
|
|
gbs_cur_cfg["recompute_granularity"],
|
|
)
|
|
ctx.args.log_dir = log_dir
|
|
|
|
# generate script args of task
|
|
gbs_new_args = gen_new_args(
|
|
raw_args, gbs_cur_cfg, gbs_tuner_cfg
|
|
)
|
|
ctx.args.training_script_args = gbs_new_args
|
|
|
|
# launch task
|
|
ctx.logger.info(
|
|
f"Launch task from auto tuner: job_id {task_job_id}, log_dir {log_dir}, config {gbs_cur_cfg}"
|
|
)
|
|
logger.info(
|
|
f"Launch task from auto tuner: job_id {task_job_id}, log_dir {log_dir}, config {gbs_cur_cfg}"
|
|
)
|
|
c = controllers.init(ctx)
|
|
c.run()
|
|
|
|
# process generated result
|
|
# TODO differentiate out of memory and no loss(maybe over time)
|
|
# TODO integrate memory and metric read
|
|
metric, mem, err = read_log(
|
|
path=ctx.args.log_dir,
|
|
metric_file="workerlog.0",
|
|
target_metric=tuner_cfg["metric_cfg"]["name"],
|
|
memory_file=f"{ctx.args.job_id}.gpu.log",
|
|
)
|
|
|
|
if err & (1 << 0):
|
|
ctx.logger.warning(
|
|
f"Read metric failed for parameters: {log_dir}"
|
|
)
|
|
logger.warning(
|
|
f"Read metric failed for parameters: {log_dir}"
|
|
)
|
|
# for pruner use
|
|
gbs_cur_cfg['time'] = -1
|
|
gbs_cur_cfg[tuner_cfg['metric_cfg']['name']] = None
|
|
gbs_cur_cfg["max_mem_usage"] = mem
|
|
|
|
if err & (1 << 1):
|
|
ctx.logger.warning(
|
|
f"Out of memory for parameters: {log_dir}"
|
|
)
|
|
logger.warning(f"Out of memory for parameters: {log_dir}")
|
|
# for pruner use
|
|
gbs_cur_cfg['time'] = -1
|
|
gbs_cur_cfg[tuner_cfg['metric_cfg']['name']] = None
|
|
gbs_cur_cfg["max_mem_usage"] = "OOM"
|
|
|
|
# not err & (1 << 1): do not record memory usage when out of memory
|
|
if err & (1 << 2) and not err & (1 << 1):
|
|
ctx.logger.warning(
|
|
f"Read memory usage failed for parameters: {log_dir}"
|
|
)
|
|
logger.warning(
|
|
f"Read memory usage failed for parameters: {log_dir}"
|
|
)
|
|
gbs_cur_cfg["max_mem_usage"] = None
|
|
|
|
if not err:
|
|
# for pruner use
|
|
gbs_cur_cfg['time'] = metric
|
|
gbs_cur_cfg[tuner_cfg['metric_cfg']['name']] = metric
|
|
gbs_cur_cfg["max_mem_usage"] = mem
|
|
|
|
if err & (1 << 0) or err & (1 << 1):
|
|
# no metric or out of memory, end gbs search
|
|
break
|
|
|
|
# store and update args for next round
|
|
gbs_cur_cfg["job_id"] = job_id
|
|
best_gbs = gbs_cur_cfg["global_batch_size"]
|
|
recorder.add_cfg(**gbs_cur_cfg)
|
|
c.finalize(exit=False)
|
|
recorder.store_history("./tuner_gbs_history.csv")
|
|
|
|
# new cfgs for next round
|
|
gbs_new_cfg = gbs_tuner.search_once()
|
|
gbs_cur_cfg = copy.deepcopy(gbs_new_cfg)
|
|
gbs_tuner.add_cfg(gbs_cur_cfg)
|
|
|
|
# per task launch interval
|
|
time.sleep(3)
|
|
# prevent no valid global batch size found
|
|
if best_gbs is None:
|
|
raise ValueError(
|
|
f"No valid global batch size found, check memory or valid search time. cur_tuner_cfg{gbs_tuner_cfg}"
|
|
)
|
|
# set best global batch size to tuner cfg
|
|
tuner_cfg["model_cfg"]["global_batch_size"] = best_gbs
|
|
|
|
recorder.store_history("./tuner_gbs_history.csv")
|
|
recorder.clean_history()
|
|
|
|
end_time = time.time()
|
|
ctx.logger.info(
|
|
f"AutoTuner for GBS search ends in {end_time - start_time}s."
|
|
)
|
|
logger.info(
|
|
f"AutoTuner for GBS search ends in {end_time - start_time}s."
|
|
)
|
|
|
|
# build AutoTuner to get new config
|
|
auto_tuner = AutoTuner(tuner_cfg)
|
|
logger.info(
|
|
f"Launch {len(auto_tuner.algo.all_tasks)} tasks by auto tuner: "
|
|
)
|
|
resume_csv_file_path = tuner_cfg.get(
|
|
"resume_csv_file_path", history_file_path
|
|
)
|
|
auto_tuner.resume_form_history(resume_csv_file_path)
|
|
cur_cfg = auto_tuner.search_once()
|
|
auto_tuner.add_cfg(cur_cfg)
|
|
error_msg = (
|
|
"No config can search. Please check if there are any situations "
|
|
+ "where GBS is unable to divide dp degree or shading degree, "
|
|
+ "or if there are related configurations of the model such as "
|
|
+ "hidden_size cannot be evenly divided by mp degree, "
|
|
+ "num_ Layers cannot divide pp degree."
|
|
)
|
|
|
|
assert cur_cfg is not None, error_msg
|
|
while cur_cfg:
|
|
task_start_time = time.time()
|
|
ctx = copy.deepcopy(raw_ctx)
|
|
if is_first_task:
|
|
ctx.max_time_per_task = warmup_time
|
|
is_first_task = False
|
|
# auto tuner supports dp, mp, pp, micro batch size, sharding, recompute by default and every task has own log dir
|
|
global_batch_size = (
|
|
cur_cfg["global_batch_size"]
|
|
if "global_batch_size" in cur_cfg
|
|
else tuner_cfg["model_cfg"]["global_batch_size"]
|
|
)
|
|
acc_steps = (
|
|
global_batch_size
|
|
// cur_cfg["dp_degree"]
|
|
// cur_cfg["sharding_degree"]
|
|
// cur_cfg["micro_batch_size"]
|
|
)
|
|
cur_cfg["acc_steps"] = acc_steps
|
|
cur_cfg["global_batch_size"] = global_batch_size
|
|
|
|
# every task has own job id
|
|
job_id += 1
|
|
task_job_id = "auto_tuner_" + str(job_id)
|
|
ctx.args.job_id = task_job_id
|
|
log_dir = "Job{}_GBS{}_DP{}_MP{}_PP{}_VPP{}_Sharding{}_Stage{}_MBS{}_Recompute_{}_Granularity_{}_AccStep{}".format(
|
|
job_id,
|
|
global_batch_size,
|
|
cur_cfg["dp_degree"],
|
|
cur_cfg["mp_degree"],
|
|
cur_cfg["pp_degree"],
|
|
cur_cfg["vpp_degree"],
|
|
cur_cfg["sharding_degree"],
|
|
cur_cfg["sharding_stage"],
|
|
cur_cfg["micro_batch_size"],
|
|
cur_cfg["use_recompute"],
|
|
cur_cfg["recompute_granularity"],
|
|
cur_cfg["acc_steps"],
|
|
)
|
|
if "sharding_overlap" in cur_cfg:
|
|
log_dir = log_dir + f"_Overlap_{cur_cfg['sharding_overlap']}"
|
|
if "refined_recompute" in tuner_cfg:
|
|
for key in tuner_cfg["refined_recompute"]:
|
|
dir_name = "".join(i.capitalize() for i in key.split("_"))
|
|
dir_name += str(cur_cfg[key])
|
|
log_dir = log_dir + "_" + dir_name
|
|
|
|
if "custom_search_dim" in tuner_cfg:
|
|
for key in tuner_cfg["custom_search_dim"]:
|
|
dir_name = "".join(i.capitalize() for i in key.split("_"))
|
|
dir_name += str(cur_cfg[key])
|
|
log_dir = log_dir + "_" + dir_name
|
|
|
|
ctx.args.log_dir = os.path.join(
|
|
os.path.dirname(ctx.args.auto_tuner_json), log_dir
|
|
)
|
|
|
|
# generate the script arguments and launch configuration JSON/YAML for the task.
|
|
cur_cfg["log_dir_name"] = log_dir
|
|
new_args = gen_new_args(raw_args, cur_cfg, tuner_cfg)
|
|
ctx.args.training_script_args = new_args
|
|
cur_cfg.pop("log_dir_name")
|
|
|
|
# launch task
|
|
ctx.logger.info(
|
|
f"Launch task: job_id {task_job_id}, log_dir {log_dir}"
|
|
)
|
|
logger.info(f"Launch task: job_id {task_job_id}, log_dir {log_dir}")
|
|
|
|
cur_resume_cfg = auto_tuner.get_cfg_from_resume(cur_cfg)
|
|
if cur_resume_cfg:
|
|
cur_cfg = cur_resume_cfg
|
|
cur_cfg['job_id'] = job_id
|
|
auto_tuner.history_cfgs.pop(-1)
|
|
auto_tuner.add_cfg(cur_cfg)
|
|
if (
|
|
recorder.additional_metric_key is None
|
|
and "additional_metric_key" in cur_cfg
|
|
):
|
|
recorder.additional_metric_key = cur_cfg[
|
|
"additional_metric_key"
|
|
]
|
|
recorder.add_cfg(**cur_cfg)
|
|
cur_best_cfgs, err = recorder.get_best(
|
|
metric=tuner_cfg['metric_cfg']['name'],
|
|
direction=tuner_cfg['metric_cfg']['OptimizationDirection'],
|
|
buffer=buffer,
|
|
max_mem_usage=max_mem_usage,
|
|
)
|
|
if not err:
|
|
to_json_str = json.dumps(cur_best_cfgs)
|
|
ctx.logger.info(f"Current best config: {to_json_str}")
|
|
logger.info(f"Current best config: {to_json_str}")
|
|
else:
|
|
ctx.logger.info(
|
|
"Get best config failed. Currently no config can be run."
|
|
)
|
|
logger.info(
|
|
"Get best config failed. Currently no config can be run."
|
|
)
|
|
if cur_cfg.get("sharding_overlap"):
|
|
add_overlap_performance(
|
|
cur_cfg, tuner_cfg, recorder.history
|
|
)
|
|
|
|
if cur_cfg["error_info"]:
|
|
error_task_nums += 1
|
|
error_info = cur_cfg["error_info"]
|
|
task_nums = len(auto_tuner.algo.all_tasks)
|
|
cur_task_id = auto_tuner.algo.idx
|
|
ctx.logger.info(
|
|
"Auto Tuner Schedule: [{}/{}], Pruned nums {}, Error nums {}, Error info {}, Remaining time {} min".format(
|
|
cur_task_id,
|
|
task_nums,
|
|
cur_task_id - job_id,
|
|
error_task_nums,
|
|
error_info,
|
|
round(
|
|
(task_nums - cur_task_id) * max_time_per_task / 60,
|
|
2,
|
|
),
|
|
)
|
|
)
|
|
logger.info(
|
|
"Auto Tuner Schedule: [{}/{}], Pruned nums {}, Error nums {}, Error info {}, Remaining time {} min".format(
|
|
cur_task_id,
|
|
task_nums,
|
|
cur_task_id - job_id,
|
|
error_task_nums,
|
|
error_info,
|
|
round(
|
|
(task_nums - cur_task_id) * max_time_per_task / 60,
|
|
2,
|
|
),
|
|
)
|
|
)
|
|
recorder.store_history(history_file_path)
|
|
# generate a new config
|
|
new_cfg = auto_tuner.search_once()
|
|
cur_cfg = copy.deepcopy(new_cfg)
|
|
auto_tuner.add_cfg(cur_cfg)
|
|
continue
|
|
|
|
# in single dp estimation scene, just some nodes not all nodes run
|
|
ctx = gen_new_ctx(ctx, cur_cfg, tuner_cfg)
|
|
actual_nnodes = (
|
|
int(ctx.args.nnodes.split(":")[0])
|
|
if not isinstance(ctx.args.nnodes, int)
|
|
else ctx.args.nnodes
|
|
)
|
|
if sorted_ips:
|
|
actual_exec_ips = sorted_ips[:actual_nnodes]
|
|
if ip not in actual_exec_ips:
|
|
cur_cfg = client.get(f"auto_tuner/{log_dir}")[0]
|
|
wait_start_time = time.time()
|
|
while not cur_cfg:
|
|
wait_end_time = time.time()
|
|
if (
|
|
wait_end_time - wait_start_time
|
|
> tuner_cfg["max_time_per_task"] + 30
|
|
):
|
|
raise ValueError(f"Wait {log_dir} failed")
|
|
time.sleep(3)
|
|
cur_cfg = client.get(f"auto_tuner/{log_dir}")[0]
|
|
logger.info(
|
|
f"Receive that task {log_dir} has ended by etcd."
|
|
)
|
|
ctx.logger.info(
|
|
f"Receive that task {log_dir} has ended by etcd."
|
|
)
|
|
cur_cfg = json.loads(cur_cfg.decode())
|
|
auto_tuner.history_cfgs.pop(-1)
|
|
auto_tuner.add_cfg(cur_cfg)
|
|
if (
|
|
recorder.additional_metric_key is None
|
|
and "additional_metric_key" in cur_cfg
|
|
):
|
|
recorder.additional_metric_key = cur_cfg[
|
|
"additional_metric_key"
|
|
]
|
|
recorder.add_cfg(**cur_cfg)
|
|
cur_best_cfgs, err = recorder.get_best(
|
|
metric=tuner_cfg['metric_cfg']['name'],
|
|
direction=tuner_cfg['metric_cfg'][
|
|
'OptimizationDirection'
|
|
],
|
|
buffer=buffer,
|
|
max_mem_usage=max_mem_usage,
|
|
)
|
|
if not err:
|
|
to_json_str = json.dumps(cur_best_cfgs)
|
|
ctx.logger.info(f"Current best config: {to_json_str}")
|
|
logger.info(f"Current best config: {to_json_str}")
|
|
else:
|
|
ctx.logger.info(
|
|
"Get best config failed. Currently no config can be run."
|
|
)
|
|
logger.info(
|
|
"Get best config failed. Currently no config can be run."
|
|
)
|
|
if cur_cfg.get("sharding_overlap"):
|
|
add_overlap_performance(
|
|
cur_cfg, tuner_cfg, recorder.history
|
|
)
|
|
has_error = cur_cfg["has_error"]
|
|
if has_error:
|
|
error_task_nums += 1
|
|
error_info = cur_cfg["error_info"]
|
|
task_nums = len(auto_tuner.algo.all_tasks)
|
|
cur_task_id = auto_tuner.algo.idx
|
|
ctx.logger.info(
|
|
"Auto Tuner Schedule: [{}/{}], Pruned nums {}, Error nums {}, Error info {}, Remaining time {} min".format(
|
|
cur_task_id,
|
|
task_nums,
|
|
cur_task_id - job_id,
|
|
error_task_nums,
|
|
error_info,
|
|
round(
|
|
(task_nums - cur_task_id)
|
|
* max_time_per_task
|
|
/ 60,
|
|
2,
|
|
),
|
|
)
|
|
)
|
|
logger.info(
|
|
"Auto Tuner Schedule: [{}/{}], Pruned nums {}, Error nums {}, Error info {}, Remaining time {} min".format(
|
|
cur_task_id,
|
|
task_nums,
|
|
cur_task_id - job_id,
|
|
error_task_nums,
|
|
error_info,
|
|
round(
|
|
(task_nums - cur_task_id)
|
|
* max_time_per_task
|
|
/ 60,
|
|
2,
|
|
),
|
|
)
|
|
)
|
|
recorder.store_history(history_file_path)
|
|
# generate a new config
|
|
new_cfg = auto_tuner.search_once()
|
|
cur_cfg = copy.deepcopy(new_cfg)
|
|
auto_tuner.add_cfg(cur_cfg)
|
|
continue
|
|
|
|
# for single dp estimation and not run sharding overlap
|
|
if tuner_cfg["search_algo"]["name"] != "grid":
|
|
# estimated_num_gpus means need single dp estimation
|
|
bypass_optimizer_flag = "0"
|
|
if (
|
|
"estimated_num_gpus" in tuner_cfg["search_algo"]
|
|
and cur_cfg["sharding_degree"] == 1
|
|
):
|
|
bypass_optimizer_flag = "1"
|
|
ctx.set_envs(
|
|
{
|
|
"FLAGS_shard_bypass_dygraph_optimizer": bypass_optimizer_flag
|
|
}
|
|
)
|
|
c = controllers.init(ctx)
|
|
c.run()
|
|
|
|
task_end_time = time.time()
|
|
cur_cfg["exec_time"] = round(task_end_time - task_start_time, 2)
|
|
ctx.logger.info(
|
|
"Task: job_id {}, log_dir {} ended in {}s".format(
|
|
task_job_id, log_dir, cur_cfg["exec_time"]
|
|
)
|
|
)
|
|
logger.info(
|
|
"Task: job_id {}, log_dir {} ended in {}s".format(
|
|
task_job_id, log_dir, cur_cfg["exec_time"]
|
|
)
|
|
)
|
|
# process generated result
|
|
|
|
metric, mem, err = read_log(
|
|
path=ctx.args.log_dir,
|
|
metric_file="workerlog.0",
|
|
target_metric=tuner_cfg["metric_cfg"]["name"],
|
|
memory_file=f"{ctx.args.job_id}.gpu.log",
|
|
)
|
|
# sync sigint
|
|
timeout_flag = True
|
|
OOM_flag = err & (1 << 1)
|
|
if actual_nnodes > 1:
|
|
path = f"auto_tuner/{job_id}/{ip}"
|
|
completed = read_completed(ctx.args.log_dir)
|
|
if OOM_flag:
|
|
while not client.put(path, "OOM".encode('latin-1')):
|
|
time.sleep(1)
|
|
ctx.logger.info(f"Put OOM to {path}")
|
|
logger.info(f"Put OOM to {path}")
|
|
elif completed:
|
|
while not client.put(path, "OK".encode('latin-1')):
|
|
time.sleep(1)
|
|
ctx.logger.info(f"Put OK to {path}")
|
|
logger.info(f"Put OK to {path}")
|
|
elif hasattr(c, 'sigint') and c.sigint == 14:
|
|
while not client.put(path, "OK".encode('latin-1')):
|
|
time.sleep(1)
|
|
ctx.logger.info(f"Put OK to {path}")
|
|
logger.info(f"Put OK to {path}")
|
|
elif not hasattr(c, 'sigint') and c.pod.exit_code == 0:
|
|
while not client.put(path, "OK".encode('latin-1')):
|
|
time.sleep(1)
|
|
ctx.logger.info(f"Put OK to {path}")
|
|
logger.info(f"Put OK to {path}")
|
|
else:
|
|
while not client.put(path, "Error".encode('latin-1')):
|
|
time.sleep(1)
|
|
ctx.logger.info(f"Put Error to {path}")
|
|
logger.info(f"Put Error to {path}")
|
|
|
|
result = list(client.get_prefix(f"auto_tuner/{job_id}/"))
|
|
size = len(result)
|
|
while size != actual_nnodes:
|
|
time.sleep(1)
|
|
result = list(client.get_prefix(f"auto_tuner/{job_id}/"))
|
|
size = len(result)
|
|
|
|
status = [i[0].decode() for i in result]
|
|
ctx.logger.info(f"Status of auto_tuner/{job_id}/: {status}")
|
|
logger.info(f"Status of auto_tuner/{job_id}/: {status}")
|
|
|
|
if "OOM" in status:
|
|
timeout_flag = False
|
|
OOM_flag = True
|
|
elif "OK" not in status:
|
|
timeout_flag = False
|
|
|
|
has_error = False
|
|
if err & (1 << 0):
|
|
ctx.logger.warning(f"Read metric of {log_dir} failed.")
|
|
logger.warning(f"Read metric of {log_dir} failed.")
|
|
# for pruner use
|
|
cur_cfg['time'] = -1
|
|
cur_cfg[tuner_cfg['metric_cfg']['name']] = None
|
|
cur_cfg["max_mem_usage"] = mem if not OOM_flag else "OOM"
|
|
has_error = True
|
|
|
|
if err & (1 << 1):
|
|
ctx.logger.warning(f"{log_dir} OOM.")
|
|
logger.warning(f"{log_dir} OOM.")
|
|
# for pruner use
|
|
cur_cfg['time'] = -1
|
|
cur_cfg[tuner_cfg['metric_cfg']['name']] = None
|
|
cur_cfg["max_mem_usage"] = "OOM"
|
|
has_error = True
|
|
|
|
# not err & (1 << 1): do not record memory usage when out of memory
|
|
if err & (1 << 2) and not err & (1 << 1):
|
|
ctx.logger.warning(f"Read memory usage of {log_dir} failed.")
|
|
logger.warning(f"Read memory usage of {log_dir} failed.")
|
|
cur_cfg["max_mem_usage"] = None if not OOM_flag else "OOM"
|
|
|
|
if not has_error and timeout_flag:
|
|
# for pruner use
|
|
cur_cfg['time'] = metric
|
|
cur_cfg[tuner_cfg['metric_cfg']['name']] = metric
|
|
cur_cfg["max_mem_usage"] = mem if not OOM_flag else "OOM"
|
|
|
|
if not has_error and not timeout_flag:
|
|
cur_cfg['time'] = -1
|
|
cur_cfg[tuner_cfg['metric_cfg']['name']] = None
|
|
cur_cfg["max_mem_usage"] = None if not OOM_flag else "OOM"
|
|
|
|
if tuner_cfg['metric_cfg']['name'] not in cur_cfg:
|
|
cur_cfg[tuner_cfg['metric_cfg']['name']] = None
|
|
|
|
path = f"auto_tuner/mem/{job_id}/{ip}"
|
|
if nnodes > 1:
|
|
while not client.put(
|
|
path, str(cur_cfg["max_mem_usage"]).encode('latin-1')
|
|
):
|
|
time.sleep(1)
|
|
result = list(client.get_prefix(f"auto_tuner/mem/{job_id}"))
|
|
size = len(result)
|
|
while size != nnodes:
|
|
time.sleep(1)
|
|
result = list(
|
|
client.get_prefix(f"auto_tuner/mem/{job_id}/")
|
|
)
|
|
size = len(result)
|
|
mem_allnodes = [i[0].decode() for i in result]
|
|
|
|
for mem in mem_allnodes:
|
|
if mem is None or cur_cfg["max_mem_usage"] is None:
|
|
continue
|
|
if mem == "OOM":
|
|
cur_cfg["max_mem_usage"] = mem
|
|
break
|
|
cur_cfg["max_mem_usage"] = max(
|
|
int(float(mem)), int(float(cur_cfg["max_mem_usage"]))
|
|
)
|
|
|
|
# if need accurate peak memory
|
|
if os.environ.get("FLAGS_log_memory_stats", False):
|
|
max_peak_memory = None
|
|
from paddle.distributed.auto_tuner.utils import (
|
|
read_allocated_memory_log,
|
|
)
|
|
|
|
for root, dirs, files in os.walk(ctx.args.log_dir):
|
|
for file in files:
|
|
if not file.startswith("workerlog"):
|
|
continue
|
|
peak_memory = read_allocated_memory_log(
|
|
ctx.args.log_dir, file
|
|
)
|
|
if peak_memory is not None and max_peak_memory is None:
|
|
max_peak_memory = peak_memory
|
|
elif peak_memory and max_peak_memory:
|
|
if peak_memory > max_peak_memory:
|
|
max_peak_memory = peak_memory
|
|
cur_cfg["max_peak_memory"] = max_peak_memory
|
|
|
|
cur_cfg['job_id'] = job_id
|
|
|
|
# multi dp conversion
|
|
if (
|
|
"conversion" in tuner_cfg["search_algo"]
|
|
and "step_time" in tuner_cfg["search_algo"]["conversion"]
|
|
and "sharding_overlap" not in cur_cfg
|
|
):
|
|
single_dp_performance = cur_cfg[tuner_cfg['metric_cfg']['name']]
|
|
step_time_metric = tuner_cfg["search_algo"]["conversion"][
|
|
"step_time"
|
|
]
|
|
step_time = read_step_time_log(
|
|
path=ctx.args.log_dir,
|
|
file="workerlog.0",
|
|
target_metric=step_time_metric,
|
|
)
|
|
|
|
# set default
|
|
comm_bw = tuner_cfg["search_algo"]["conversion"].get(
|
|
"comm_bw", [100]
|
|
)
|
|
model_size_b = int(
|
|
tuner_cfg["search_algo"]["conversion"].get(
|
|
"model_size_b", 7
|
|
)
|
|
)
|
|
amp = tuner_cfg["search_algo"]["conversion"].get("amp", False)
|
|
num_gpus = int(cur_cfg["num_gpus"])
|
|
seq_length = int(
|
|
tuner_cfg["model_cfg"].get("max_seq_length", 2048)
|
|
)
|
|
cur_cfg[f"unified_{tuner_cfg['metric_cfg']['name']}"] = (
|
|
round(single_dp_performance / num_gpus, 2)
|
|
if single_dp_performance
|
|
and tuner_cfg["search_algo"]["conversion"].get(
|
|
"need_unify", False
|
|
)
|
|
else single_dp_performance
|
|
)
|
|
for bw in comm_bw:
|
|
if amp:
|
|
comm_time = model_size_b * (4 + 2) / bw
|
|
else:
|
|
comm_time = model_size_b * 4 / bw
|
|
multi_dp_performance = (
|
|
round(
|
|
step_time
|
|
/ (step_time + comm_time)
|
|
* single_dp_performance,
|
|
5,
|
|
)
|
|
if single_dp_performance and step_time
|
|
else None
|
|
)
|
|
cur_cfg[f"bw_{bw}_{tuner_cfg['metric_cfg']['name']}"] = (
|
|
multi_dp_performance
|
|
)
|
|
cur_cfg[
|
|
f"unified_bw_{bw}_{tuner_cfg['metric_cfg']['name']}"
|
|
] = (
|
|
round(multi_dp_performance / num_gpus, 2)
|
|
if multi_dp_performance
|
|
and tuner_cfg["search_algo"]["conversion"].get(
|
|
"need_unify", False
|
|
)
|
|
else multi_dp_performance
|
|
)
|
|
if recorder.additional_metric_key is None:
|
|
recorder.additional_metric_key = (
|
|
f"unified_bw_{bw}_{tuner_cfg['metric_cfg']['name']}"
|
|
)
|
|
cur_cfg["additional_metric_key"] = (
|
|
recorder.additional_metric_key
|
|
)
|
|
|
|
error_info = None
|
|
cur_cfg["has_error"] = has_error
|
|
if has_error:
|
|
error_info = []
|
|
error_task_nums += 1
|
|
if OOM_flag:
|
|
error_info.append("Out of memory")
|
|
else:
|
|
if actual_nnodes > 1:
|
|
path = f"auto_tuner/error/{job_id}/{ip}"
|
|
single_error_info = find_error_from_log(
|
|
ctx.args.log_dir
|
|
)
|
|
if len(single_error_info) > 0:
|
|
while not client.put(
|
|
path,
|
|
single_error_info.encode('latin-1', 'ignore'),
|
|
):
|
|
time.sleep(1)
|
|
ctx.logger.info(
|
|
f"Put Error info: {single_error_info} to {path}"
|
|
)
|
|
logger.info(
|
|
f"Put Error info: {single_error_info} to {path}"
|
|
)
|
|
else:
|
|
while not client.put(path, "OK".encode('latin-1')):
|
|
time.sleep(1)
|
|
ctx.logger.info(f"Put OK to {path}")
|
|
logger.info(f"Put OK to {path}")
|
|
|
|
result = list(
|
|
client.get_prefix(f"auto_tuner/error/{job_id}/")
|
|
)
|
|
size = len(result)
|
|
while size != actual_nnodes:
|
|
time.sleep(1)
|
|
result = list(
|
|
client.get_prefix(f"auto_tuner/error/{job_id}/")
|
|
)
|
|
size = len(result)
|
|
|
|
status = [
|
|
i[0].decode()
|
|
for i in result
|
|
if "OK" not in i[0].decode('utf-8', 'ignore')
|
|
]
|
|
error_info = list(set(status))
|
|
ctx.logger.info(
|
|
f"Status of auto_tuner/error/{job_id}/: {error_info}"
|
|
)
|
|
logger.info(
|
|
f"Status of auto_tuner/error/{job_id}/: {error_info}"
|
|
)
|
|
else:
|
|
error_info.append(find_error_from_log(ctx.args.log_dir))
|
|
cur_cfg["error_info"] = error_info
|
|
task_nums = len(auto_tuner.algo.all_tasks)
|
|
cur_task_id = auto_tuner.algo.idx
|
|
ctx.logger.info(
|
|
"Auto Tuner Schedule: [{}/{}], Pruned nums {}, Error nums {}, Error info {}, Remaining time {} min".format(
|
|
cur_task_id,
|
|
task_nums,
|
|
cur_task_id - job_id,
|
|
error_task_nums,
|
|
error_info,
|
|
round(
|
|
(task_nums - cur_task_id) * max_time_per_task / 60,
|
|
2,
|
|
),
|
|
)
|
|
)
|
|
logger.info(
|
|
"Auto Tuner Schedule: [{}/{}], Pruned nums {}, Error nums {}, Error info {}, Remaining time {} min".format(
|
|
cur_task_id,
|
|
task_nums,
|
|
cur_task_id - job_id,
|
|
error_task_nums,
|
|
error_info,
|
|
round(
|
|
(task_nums - cur_task_id) * max_time_per_task / 60,
|
|
2,
|
|
),
|
|
)
|
|
)
|
|
|
|
# sync for single dp
|
|
if sorted_ips:
|
|
master_ip = sorted_ips[0]
|
|
if ip == master_ip:
|
|
while not client.put(
|
|
f"auto_tuner/{log_dir}",
|
|
json.dumps(cur_cfg).encode('latin-1'),
|
|
):
|
|
time.sleep(1)
|
|
logger.info(f"{ip} put auto_tuner/{log_dir} successfully.")
|
|
recorder.add_cfg(**cur_cfg)
|
|
cur_best_cfgs, err = recorder.get_best(
|
|
metric=tuner_cfg['metric_cfg']['name'],
|
|
direction=tuner_cfg['metric_cfg']['OptimizationDirection'],
|
|
buffer=buffer,
|
|
max_mem_usage=max_mem_usage,
|
|
)
|
|
if not err:
|
|
to_json_str = json.dumps(cur_best_cfgs)
|
|
ctx.logger.info(f"Current best config: {to_json_str}")
|
|
logger.info(f"Current best config: {to_json_str}")
|
|
else:
|
|
ctx.logger.info("Get best config failed, no config can be run.")
|
|
logger.info("Get best config failed, no config can be run.")
|
|
|
|
# record history
|
|
if cur_cfg.get("sharding_overlap"):
|
|
add_overlap_performance(cur_cfg, tuner_cfg, recorder.history)
|
|
|
|
recorder.store_history(history_file_path)
|
|
c.finalize(exit=False)
|
|
|
|
# generate a new config
|
|
new_cfg = auto_tuner.search_once()
|
|
cur_cfg = copy.deepcopy(new_cfg)
|
|
auto_tuner.add_cfg(cur_cfg)
|
|
|
|
# per task launch interval
|
|
self_pid = os.getpid()
|
|
if paddle.device.is_compiled_with_custom_device('npu'):
|
|
processes = os.popen(
|
|
"fuser -v /dev/davinci* |awk '{for(i=1;i<=NF;i++) print $i;}'"
|
|
).readlines()
|
|
elif paddle.is_compiled_with_xpu():
|
|
processes = os.popen(
|
|
"fuser -v /dev/xpu* |awk '{for(i=1;i<=NF;i++) print $i;}'"
|
|
).readlines()
|
|
else:
|
|
processes = os.popen(
|
|
"fuser -v /dev/nvidia* |awk '{for(i=1;i<=NF;i++) print $i;}'"
|
|
).readlines()
|
|
pids_to_kill = launch_utils.filter_pids(processes, self_pid)
|
|
launch_utils.terminate_processes(pids_to_kill)
|
|
time.sleep(3)
|
|
end_time = time.time()
|
|
|
|
# keep cluster exit consistency
|
|
path = f"auto_tuner/exit/{job_id}/{ip}"
|
|
if max_search_time and (end_time - start_time) > int(
|
|
max_search_time
|
|
):
|
|
if nnodes > 1:
|
|
while not client.put(path, "error".encode('latin-1')):
|
|
time.sleep(1)
|
|
else:
|
|
break
|
|
else:
|
|
if nnodes > 1:
|
|
while not client.put(path, "ok".encode('latin-1')):
|
|
time.sleep(1)
|
|
|
|
if nnodes > 1:
|
|
result = list(client.get_prefix(f"auto_tuner/exit/{job_id}"))
|
|
size = len(result)
|
|
while size != nnodes:
|
|
time.sleep(1)
|
|
result = list(
|
|
client.get_prefix(f"auto_tuner/exit/{job_id}/")
|
|
)
|
|
size = len(result)
|
|
status = [i[0].decode() for i in result]
|
|
|
|
if "error" in status:
|
|
break
|
|
|
|
recorder.store_history(history_file_path)
|
|
|
|
# get best config to run
|
|
best_cfg = None
|
|
ctx = copy.deepcopy(raw_ctx)
|
|
if nnodes > 1:
|
|
collective_master_ip = os.environ.get("COLLECTIVE_MASTER_IP", None)
|
|
assert collective_master_ip is not None
|
|
if ip == collective_master_ip:
|
|
best_cfg, err = recorder.get_best(
|
|
metric=tuner_cfg['metric_cfg']['name'],
|
|
direction=tuner_cfg['metric_cfg']['OptimizationDirection'],
|
|
buffer=buffer,
|
|
max_mem_usage=max_mem_usage,
|
|
)
|
|
if err:
|
|
raise ValueError(
|
|
"Get best config failed. Currently there are no appropriate configs."
|
|
)
|
|
data = json.dumps(best_cfg)
|
|
while not client.put("best_cfg", data):
|
|
time.sleep(1)
|
|
continue
|
|
else:
|
|
for i in range(10):
|
|
try:
|
|
data = client.get("best_cfg")[0].decode()
|
|
best_cfg = json.loads(data)
|
|
except Exception as e:
|
|
ctx.logger.warning(e)
|
|
logger.warning(e)
|
|
time.sleep(2)
|
|
if best_cfg:
|
|
break
|
|
assert best_cfg
|
|
else:
|
|
best_cfg, err = recorder.get_best(
|
|
metric=tuner_cfg['metric_cfg']['name'],
|
|
direction=tuner_cfg['metric_cfg']['OptimizationDirection'],
|
|
buffer=buffer,
|
|
max_mem_usage=max_mem_usage,
|
|
)
|
|
if err:
|
|
raise ValueError(
|
|
"Get best config failed. Currently there are no appropriate configs."
|
|
)
|
|
assert best_cfg and best_cfg["time"] != -1
|
|
|
|
end_time = time.time()
|
|
ctx.logger.info(f"AutoTuner ended in {end_time - start_time}s.")
|
|
logger.info(f"AutoTuner ended in {end_time - start_time}s.")
|
|
# launch best cfg
|
|
# estimation search need not run best cfg
|
|
if not tuner_cfg.get("run_best", True) or tuner_cfg["search_algo"].get(
|
|
"estimated_num_gpus", None
|
|
):
|
|
sys.exit()
|
|
new_args = gen_new_args(raw_args, best_cfg, tuner_cfg, run_best=True)
|
|
ctx.run_best = True
|
|
ctx.args.training_script_args = new_args
|
|
ctx.args.job_id = "best_cfg"
|
|
to_json_str = json.dumps(best_cfg)
|
|
ctx.logger.info(f"Launch best cfg: {to_json_str}")
|
|
logger.info(f"Launch best cfg: {to_json_str}")
|
|
|
|
if tuner_cfg.get("best_cfg_dir", None):
|
|
ctx.args.log_dir = tuner_cfg["best_cfg_dir"]
|
|
else:
|
|
ctx.args.log_dir = os.path.join(
|
|
os.path.dirname(ctx.args.auto_tuner_json), "best_cfg"
|
|
)
|
|
# run best cfg
|
|
c = controllers.init(ctx)
|
|
c.run()
|
|
c.finalize(exit=True)
|
|
|
|
else:
|
|
from paddle.distributed.launch import controllers
|
|
|
|
# initialize the selected controller
|
|
c = controllers.init(ctx)
|
|
|
|
# run the pods
|
|
c.run()
|
|
|
|
# manager or just wait pod
|
|
c.finalize()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
launch()
|