test_dist_base.py 14.4 KB
Newer Older
X
Xin Pan 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13
#   Copyright (c) 2018 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.
14 15

from __future__ import print_function
X
Xin Pan 已提交
16 17 18 19 20 21 22
import time

import unittest
import os
import sys
import signal
import subprocess
23
import six
W
Wu Yi 已提交
24
import argparse
W
Wu Yi 已提交
25 26
import pickle
import numpy as np
T
typhoonzero 已提交
27

28 29 30
import paddle.fluid as fluid

RUN_STEP = 10
31
DEFAULT_BATCH_SIZE = 2
32

T
typhoonzero 已提交
33 34

class TestDistRunnerBase(object):
35
    def get_model(self, batch_size=DEFAULT_BATCH_SIZE):
T
typhoonzero 已提交
36 37 38
        raise NotImplementedError(
            "get_model should be implemented by child classes.")

39
    @staticmethod
W
Wu Yi 已提交
40 41 42 43 44 45
    def get_transpiler(trainer_id,
                       main_program,
                       pserver_endpoints,
                       trainers,
                       sync_mode,
                       dc_asgd=False):
T
typhoonzero 已提交
46
        # NOTE: import fluid until runtime, or else forking processes will cause error.
47
        config = fluid.DistributeTranspilerConfig()
W
Wu Yi 已提交
48
        config.enable_dc_asgd = dc_asgd
49
        t = fluid.DistributeTranspiler(config=config)
T
typhoonzero 已提交
50 51 52 53
        t.transpile(
            trainer_id=trainer_id,
            program=main_program,
            pservers=pserver_endpoints,
W
Wu Yi 已提交
54 55
            trainers=trainers,
            sync_mode=sync_mode)
T
typhoonzero 已提交
56 57
        return t

W
Wu Yi 已提交
58
    def run_pserver(self, args):
59
        self.get_model(batch_size=args.batch_size)
60
        # NOTE: pserver should not call memory optimize
W
Wu Yi 已提交
61 62
        t = self.get_transpiler(args.trainer_id,
                                fluid.default_main_program(), args.endpoints,
W
Wu Yi 已提交
63
                                args.trainers, args.sync_mode, args.dc_asgd)
W
Wu Yi 已提交
64 65 66
        pserver_prog = t.get_pserver_program(args.current_endpoint)
        startup_prog = t.get_startup_program(args.current_endpoint,
                                             pserver_prog)
Y
Yancey1989 已提交
67

T
typhoonzero 已提交
68 69 70 71 72
        place = fluid.CPUPlace()
        exe = fluid.Executor(place)
        exe.run(startup_prog)
        exe.run(pserver_prog)

73
    def run_trainer(self, args):
T
typhoonzero 已提交
74
        test_program, avg_cost, train_reader, test_reader, batch_acc, predict = \
75
            self.get_model(batch_size=args.batch_size)
76

W
Wu Yi 已提交
77
        if args.mem_opt:
78
            fluid.memory_optimize(fluid.default_main_program(), skip_grads=True)
W
Wu Yi 已提交
79 80 81 82
        if args.is_dist:
            t = self.get_transpiler(args.trainer_id,
                                    fluid.default_main_program(),
                                    args.endpoints, args.trainers,
W
Wu Yi 已提交
83
                                    args.sync_mode, args.dc_asgd)
T
typhoonzero 已提交
84 85 86 87
            trainer_prog = t.get_trainer_program()
        else:
            trainer_prog = fluid.default_main_program()

88 89 90 91 92
        if args.use_cuda:
            place = fluid.CUDAPlace(0)
        else:
            place = fluid.CPUPlace()

T
typhoonzero 已提交
93 94 95 96 97 98
        startup_exe = fluid.Executor(place)
        startup_exe.run(fluid.default_startup_program())

        strategy = fluid.ExecutionStrategy()
        strategy.num_threads = 1
        strategy.allow_op_delay = False
99

W
Wu Yi 已提交
100
        build_stra = fluid.BuildStrategy()
101 102 103 104 105
        if args.batch_merge_repeat > 1:
            pass_builder = build_stra._create_passes_from_strategy()
            mypass = pass_builder.insert_pass(
                len(pass_builder.all_passes()) - 2, "multi_batch_merge_pass")
            mypass.set_int("num_repeats", args.batch_merge_repeat)
W
Wu Yi 已提交
106 107 108 109 110 111

        if args.use_reduce:
            build_stra.reduce_strategy = fluid.BuildStrategy.ReduceStrategy.Reduce
        else:
            build_stra.reduce_strategy = fluid.BuildStrategy.ReduceStrategy.AllReduce

T
typhoonzero 已提交
112
        exe = fluid.ParallelExecutor(
113
            args.use_cuda,
W
Wu Yi 已提交
114 115 116
            loss_name=avg_cost.name,
            exec_strategy=strategy,
            build_strategy=build_stra)
T
typhoonzero 已提交
117 118 119 120 121 122 123

        feed_var_list = [
            var for var in trainer_prog.global_block().vars.values()
            if var.is_data
        ]

        feeder = fluid.DataFeeder(feed_var_list, place)
124
        reader_generator = train_reader()
T
typhoonzero 已提交
125

126 127 128 129 130 131 132 133 134 135
        def get_data():
            origin_batch = next(reader_generator)
            if args.is_dist and args.use_reader_alloc:
                new_batch = []
                for offset, item in enumerate(origin_batch):
                    if offset % 2 == args.trainer_id:
                        new_batch.append(item)
                return new_batch
            else:
                return origin_batch
T
typhoonzero 已提交
136

W
Wu Yi 已提交
137
        out_losses = []
138 139 140
        for _ in six.moves.xrange(RUN_STEP):
            loss, = exe.run(fetch_list=[avg_cost.name],
                            feed=feeder.feed(get_data()))
W
Wu Yi 已提交
141 142 143 144 145
            out_losses.append(loss[0])
        if six.PY2:
            print(pickle.dumps(out_losses))
        else:
            sys.stdout.buffer.write(pickle.dumps(out_losses))
T
typhoonzero 已提交
146 147 148


def runtime_main(test_class):
W
Wu Yi 已提交
149 150 151 152 153 154 155 156 157 158 159
    parser = argparse.ArgumentParser(description='Run dist test.')
    parser.add_argument(
        '--role', type=str, required=True, choices=['pserver', 'trainer'])
    parser.add_argument('--endpoints', type=str, required=False, default="")
    parser.add_argument('--is_dist', action='store_true')
    parser.add_argument('--trainer_id', type=int, required=False, default=0)
    parser.add_argument('--trainers', type=int, required=False, default=1)
    parser.add_argument(
        '--current_endpoint', type=str, required=False, default="")
    parser.add_argument('--sync_mode', action='store_true')
    parser.add_argument('--mem_opt', action='store_true')
160
    parser.add_argument('--use_cuda', action='store_true')
W
Wu Yi 已提交
161
    parser.add_argument('--use_reduce', action='store_true')
W
Wu Yi 已提交
162
    parser.add_argument('--dc_asgd', action='store_true')
163
    parser.add_argument(
W
Wu Yi 已提交
164
        '--use_reader_alloc', action='store_true', required=False)
165 166 167
    parser.add_argument('--batch_size', required=False, type=int, default=2)
    parser.add_argument(
        '--batch_merge_repeat', required=False, type=int, default=1)
W
Wu Yi 已提交
168 169

    args = parser.parse_args()
T
typhoonzero 已提交
170 171

    model = test_class()
W
Wu Yi 已提交
172 173
    if args.role == "pserver" and args.is_dist:
        model.run_pserver(args)
T
typhoonzero 已提交
174
    else:
175
        model.run_trainer(args)
X
Xin Pan 已提交
176

M
minqiyang 已提交
177

M
minqiyang 已提交
178
import paddle.compat as cpt
Y
Yancey1989 已提交
179 180
import socket
from contextlib import closing
M
minqiyang 已提交
181

X
Xin Pan 已提交
182 183

class TestDistBase(unittest.TestCase):
W
Wu Yi 已提交
184 185 186
    def _setup_config(self):
        raise NotImplementedError("tests should have _setup_config implemented")

187 188 189 190 191 192 193 194 195 196 197
    def _after_setup_config(self):
        if self._enforce_place == "CPU":
            self.__use_cuda = False
        elif self._enforce_place == "GPU":
            self.__use_cuda = True
        else:
            if fluid.core.is_compiled_with_cuda():
                self.__use_cuda = True
            else:
                self.__use_cuda = False

X
Xin Pan 已提交
198 199 200
    def setUp(self):
        self._trainers = 2
        self._pservers = 2
Y
Yancey1989 已提交
201 202
        self._ps_endpoints = "127.0.0.1:%s,127.0.0.1:%s" % (
            self._find_free_port(), self._find_free_port())
M
minqiyang 已提交
203
        self._python_interp = sys.executable
W
Wu Yi 已提交
204
        self._sync_mode = True
205
        self._enforce_place = None
W
Wu Yi 已提交
206
        self._mem_opt = False
W
Wu Yi 已提交
207
        self._use_reduce = False
W
Wu Yi 已提交
208
        self._dc_asgd = False  # must use with async mode
209
        self._use_reader_alloc = True
W
Wu Yi 已提交
210
        self._setup_config()
211
        self._after_setup_config()
X
Xin Pan 已提交
212

Y
Yancey1989 已提交
213 214 215 216 217
    def _find_free_port(self):
        with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
            s.bind(('', 0))
            return s.getsockname()[1]

218
    def start_pserver(self, model_file, check_error_log, required_envs):
X
Xin Pan 已提交
219
        ps0_ep, ps1_ep = self._ps_endpoints.split(",")
W
Wu Yi 已提交
220
        ps_cmd = "%s %s --role pserver --endpoints %s --trainer_id 0 --current_endpoint %s --trainers %d --is_dist"
W
Wu Yi 已提交
221
        ps0_cmd = ps_cmd % \
222 223
                  (self._python_interp, model_file, self._ps_endpoints, ps0_ep,
                   self._trainers)
W
Wu Yi 已提交
224
        ps1_cmd = ps_cmd % \
225 226
                  (self._python_interp, model_file, self._ps_endpoints, ps1_ep,
                   self._trainers)
W
Wu Yi 已提交
227 228 229 230 231 232 233

        if self._sync_mode:
            ps0_cmd += " --sync_mode"
            ps1_cmd += " --sync_mode"
        if self._mem_opt:
            ps0_cmd += " --mem_opt"
            ps1_cmd += " --mem_opt"
X
Xin Pan 已提交
234

235 236
        print(ps0_cmd)
        print(ps1_cmd)
M
minqiyang 已提交
237 238
        ps0_pipe = open("/tmp/ps0_err.log", "wb")
        ps1_pipe = open("/tmp/ps1_err.log", "wb")
G
gongweibao 已提交
239

X
Xin Pan 已提交
240
        ps0_proc = subprocess.Popen(
241 242 243 244
            ps0_cmd.strip().split(" "),
            stdout=subprocess.PIPE,
            stderr=ps0_pipe,
            env=required_envs)
X
Xin Pan 已提交
245
        ps1_proc = subprocess.Popen(
246 247 248 249
            ps1_cmd.strip().split(" "),
            stdout=subprocess.PIPE,
            stderr=ps1_pipe,
            env=required_envs)
G
gongweibao 已提交
250

251
        return ps0_proc, ps1_proc, ps0_pipe, ps1_pipe
X
Xin Pan 已提交
252

253 254 255 256 257 258
    def _run_local(self,
                   model,
                   envs,
                   check_error_log=False,
                   batch_size=DEFAULT_BATCH_SIZE,
                   batch_merge_repeat=1):
G
gongweibao 已提交
259

260
        cmd = "%s %s --role trainer" % (self._python_interp, model)
261 262 263 264
        if batch_size != DEFAULT_BATCH_SIZE:
            cmd += " --batch_size %d" % batch_size
        if batch_merge_repeat > 1:
            cmd += " --batch_merge_repeat %d" % batch_merge_repeat
265

266
        if self.__use_cuda:
267 268 269 270 271 272
            cmd += " --use_cuda"
            env_local = {"CUDA_VISIBLE_DEVICES": "0"}
        else:
            env_local = {'CPU_NUM': '1'}

        envs.update(env_local)
G
gongweibao 已提交
273

274
        if check_error_log:
275
            err_log = open("/tmp/trainer.err.log", "wb")
G
gongweibao 已提交
276
            local_proc = subprocess.Popen(
277
                cmd.split(" "),
G
gongweibao 已提交
278
                stdout=subprocess.PIPE,
279 280
                stderr=err_log,
                env=envs)
G
gongweibao 已提交
281 282
        else:
            local_proc = subprocess.Popen(
283
                cmd.split(" "),
G
gongweibao 已提交
284
                stdout=subprocess.PIPE,
285 286
                stderr=subprocess.PIPE,
                env=envs)
G
gongweibao 已提交
287

288 289 290 291 292
        local_out, local_err = local_proc.communicate()

        if check_error_log:
            err_log.close()

W
Wu Yi 已提交
293
        sys.stderr.write('local_stdout: %s\n' % pickle.loads(local_out))
294
        sys.stderr.write('local_stderr: %s\n' % local_err)
X
Xin Pan 已提交
295

W
Wu Yi 已提交
296
        return pickle.loads(local_out)
297 298

    def _run_cluster(self, model, envs, check_error_log):
X
Xin Pan 已提交
299
        # Run dist train to compare with local results
300 301
        ps0, ps1, ps0_pipe, ps1_pipe = self.start_pserver(model,
                                                          check_error_log, envs)
W
Wu Yi 已提交
302

X
Xin Pan 已提交
303
        ps0_ep, ps1_ep = self._ps_endpoints.split(",")
304

W
Wu Yi 已提交
305
        tr_cmd = "%s %s --role trainer --endpoints %s --trainer_id %d --current_endpoint %s --trainers %d --is_dist"
W
Wu Yi 已提交
306
        tr0_cmd = tr_cmd % \
307 308
                  (self._python_interp, model, self._ps_endpoints,
                   0, ps0_ep, self._trainers)
W
Wu Yi 已提交
309
        tr1_cmd = tr_cmd % \
310 311
                  (self._python_interp, model, self._ps_endpoints,
                   1, ps1_ep, self._trainers)
W
Wu Yi 已提交
312 313 314 315 316 317 318 319 320 321

        if self._sync_mode:
            tr0_cmd += " --sync_mode"
            tr1_cmd += " --sync_mode"
        if self._mem_opt:
            tr0_cmd += " --mem_opt"
            tr1_cmd += " --mem_opt"
        if self._use_reduce:
            tr0_cmd += " --use_reduce"
            tr1_cmd += " --use_reduce"
322 323 324
        if self._use_reader_alloc:
            tr0_cmd += " --use_reader_alloc"
            tr1_cmd += " --use_reader_alloc"
325
        if self.__use_cuda:
326 327 328 329 330 331 332 333 334 335
            tr0_cmd += " --use_cuda"
            tr1_cmd += " --use_cuda"
            env0 = {"CUDA_VISIBLE_DEVICES": "0"}
            env1 = {"CUDA_VISIBLE_DEVICES": "1"}
        else:
            env0 = {'CPU_NUM': '1'}
            env1 = {'CPU_NUM': '1'}

        env0.update(envs)
        env1.update(envs)
X
Xin Pan 已提交
336

W
Wu Yi 已提交
337 338
        print("tr0_cmd:{}".format(tr0_cmd))
        print("tr1_cmd:{}".format(tr1_cmd))
339 340
        tr0_pipe = open("/tmp/tr0_err.log", "wb")
        tr1_pipe = open("/tmp/tr1_err.log", "wb")
G
gongweibao 已提交
341

X
Xin Pan 已提交
342
        tr0_proc = subprocess.Popen(
W
Wu Yi 已提交
343
            tr0_cmd.strip().split(" "),
X
Xin Pan 已提交
344
            stdout=subprocess.PIPE,
G
gongweibao 已提交
345
            stderr=tr0_pipe,
X
Xin Pan 已提交
346 347
            env=env0)
        tr1_proc = subprocess.Popen(
W
Wu Yi 已提交
348
            tr1_cmd.strip().split(" "),
X
Xin Pan 已提交
349
            stdout=subprocess.PIPE,
G
gongweibao 已提交
350
            stderr=tr1_pipe,
X
Xin Pan 已提交
351 352
            env=env1)

353 354
        tr0_out, tr0_err = tr0_proc.communicate()
        tr1_out, tr1_err = tr1_proc.communicate()
X
Xin Pan 已提交
355

G
gongweibao 已提交
356
        # close trainer file
357 358
        tr0_pipe.close()
        tr1_pipe.close()
G
gongweibao 已提交
359

360 361
        ps0_pipe.close()
        ps1_pipe.close()
T
typhoonzero 已提交
362
        # FIXME: use terminate() instead of sigkill.
X
Xin Pan 已提交
363 364
        os.kill(ps0.pid, signal.SIGKILL)
        os.kill(ps1.pid, signal.SIGKILL)
W
Wu Yi 已提交
365 366
        ps0.terminate()
        ps1.terminate()
T
typhoonzero 已提交
367

368
        # print log
W
Wu Yi 已提交
369 370 371
        sys.stderr.write('trainer 0 stdout: %s\n' % pickle.loads(tr0_out))
        sys.stderr.write('trainer 0 stderr: %s\n' % tr0_err)
        sys.stderr.write('trainer 1 stdout: %s\n' % pickle.loads(tr1_out))
372 373
        sys.stderr.write('trainer 1 stderr: %s\n' % tr1_err)

W
Wu Yi 已提交
374 375
        # return tr0_losses, tr1_losses
        return pickle.loads(tr0_out), pickle.loads(tr1_out)
376 377 378 379 380 381 382 383 384 385 386 387 388

    def check_with_place(self,
                         model_file,
                         delta=1e-3,
                         check_error_log=False,
                         need_envs={}):
        # TODO(typhoonzero): should auto adapt GPU count on the machine.
        required_envs = {
            "PATH": os.getenv("PATH", ""),
            "PYTHONPATH": os.getenv("PYTHONPATH", ""),
            "LD_LIBRARY_PATH": os.getenv("LD_LIBRARY_PATH", ""),
            "FLAGS_fraction_of_gpu_memory_to_use": "0.15",
            "FLAGS_cudnn_deterministic": "1",
389
            "http_proxy": ""
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
        }

        required_envs.update(need_envs)

        if check_error_log:
            required_envs["GLOG_v"] = "7"
            required_envs["GLOG_logtostderr"] = "1"

        local_losses\
            = self._run_local(model_file, required_envs,
                                       check_error_log)
        tr0_losses, tr1_losses = self._run_cluster(model_file, required_envs,
                                                   check_error_log)

        for step_id in range(RUN_STEP):
W
Wu Yi 已提交
405 406 407 408 409 410
            local_loss = local_losses[step_id]
            tr0_loss = tr0_losses[step_id]
            tr1_loss = tr1_losses[step_id]
            dist_loss = (np.array([tr0_loss]) + np.array([tr1_loss])) / 2
            print("=======", local_loss, ":", dist_loss[0], "=======")
            self.assertAlmostEqual(local_loss, dist_loss[0], delta=delta)