parallel_executor.cc 14.8 KB
Newer Older
Y
Yang Yang 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/* Copyright (c) 2016 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. */

#include "paddle/fluid/framework/parallel_executor.h"
Y
Yu Yang 已提交
16
#include "ThreadPool.h"
Y
Yu Yang 已提交
17
#include "lod_tensor.h"
Y
Yu Yang 已提交
18
#include "lod_tensor_array.h"
Y
Yu Yang 已提交
19
#include "op_registry.h"
Y
Yu Yang 已提交
20
#include "paddle/fluid/framework/details/computation_op_handle.h"
Y
Yu Yang 已提交
21
#include "paddle/fluid/framework/details/fetch_op_handle.h"
Y
Yu Yang 已提交
22
#include "paddle/fluid/framework/details/nccl_all_reduce_op_handle.h"
Y
Yu Yang 已提交
23
#include "paddle/fluid/framework/details/op_handle_base.h"
Y
Yu Yang 已提交
24
#include "paddle/fluid/framework/details/scale_loss_grad_op_handle.h"
Y
Yu Yang 已提交
25
#include "paddle/fluid/framework/details/var_handle.h"
Y
Yu Yang 已提交
26
#include "paddle/fluid/platform/nccl_helper.h"
Y
Yang Yang 已提交
27 28

namespace paddle {
Y
Yu Yang 已提交
29 30
namespace framework {

Y
Yu Yang 已提交
31
using details::ComputationOpHandle;
Y
Yu Yang 已提交
32
using details::DummyVarHandle;
Y
Yu Yang 已提交
33
using details::FetchOpHandle;
Y
Yu Yang 已提交
34
using details::NCCLAllReduceOpHandle;
Y
Yu Yang 已提交
35
using details::OpHandleBase;
Y
Yu Yang 已提交
36
using details::ScaleLossGradOpHandle;
Y
Yu Yang 已提交
37 38
using details::VarHandle;
using details::VarHandleBase;
Y
Yu Yang 已提交
39 40 41

class ParallelExecutorPrivate {
 public:
Y
Yu Yang 已提交
42 43 44 45
  explicit ParallelExecutorPrivate(size_t num_threads,
                                   const std::vector<platform::Place> &places)
      : places_(places),
        fetch_dev_ctxs_(places),
Y
Yu Yang 已提交
46 47 48
        pool_(num_threads <= 1 ? nullptr : new ThreadPool(num_threads)) {
    vars_.resize(places.size());
  }
Y
Yu Yang 已提交
49

Y
Stash  
Yu Yang 已提交
50
  std::vector<platform::Place> places_;
Y
Yu Yang 已提交
51
  platform::DeviceContextPool fetch_dev_ctxs_;
Y
Yu Yang 已提交
52
  std::vector<Scope *> local_scopes_;
Y
Yu Yang 已提交
53
  Scope *global_scope_;
Y
Yu Yang 已提交
54

Y
Yu Yang 已提交
55
  std::unique_ptr<platform::NCCLContextMap> nccl_ctxs_;
Y
Yu Yang 已提交
56

Y
Yu Yang 已提交
57
  std::vector<std::unordered_map<std::string, std::map<int, VarHandle>>> vars_;
Y
Yu Yang 已提交
58

Y
Yu Yang 已提交
59 60
  std::unordered_set<std::unique_ptr<VarHandleBase>> dep_vars_;

Y
Yu Yang 已提交
61
  std::vector<std::unique_ptr<OpHandleBase>> ops_;
Y
Yu Yang 已提交
62

Y
Yu Yang 已提交
63
  // Use a simpler thread pool, might be faster.
Y
Yu Yang 已提交
64
  std::unique_ptr<ThreadPool> pool_;
Y
Yu Yang 已提交
65 66

  std::unique_ptr<platform::EnforceNotMet> exception_;
Y
Yu Yang 已提交
67

Y
Yu Yang 已提交
68
  VarHandle *GetVarHandle(const std::string &each_var_name,
Y
Yu Yang 已提交
69 70
                          const platform::Place &place, size_t place_offset) {
    auto &var_holders = vars_[place_offset];
Y
Yu Yang 已提交
71 72 73 74 75 76 77 78 79 80 81 82 83 84
    auto &var_holder = var_holders[each_var_name];
    VarHandle *var = nullptr;
    if (var_holder.empty()) {
      auto &init_var = var_holder[0];
      init_var.place_ = place;
      init_var.name_ = each_var_name;
      init_var.generated_op_ = nullptr;
      init_var.version_ = 0;
      var = &init_var;
    } else {
      var = &var_holder.rbegin()->second;
    }
    return var;
  }
Y
Yu Yang 已提交
85

Y
Yu Yang 已提交
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
  void RunOp(
      bool use_event,
      std::unordered_map<VarHandleBase *, std::atomic<bool>> &pending_vars,
      OpHandleBase *op) {
    std::vector<std::atomic<bool> *> *ready_buffer =
        new std::vector<std::atomic<bool> *>();
    for (auto *var : op->outputs_) {
      ready_buffer->emplace_back(&pending_vars[var]);
    }

    auto op_run = [ready_buffer, op, this, use_event] {
      try {
        VLOG(10) << op->DebugString();
        op->Run(use_event);
        for (auto *ready : *ready_buffer) {
          ready->store(true, std::memory_order_release);
        }
        delete ready_buffer;
      } catch (platform::EnforceNotMet ex) {
        exception_.reset(new platform::EnforceNotMet(ex));
      } catch (...) {
        LOG(FATAL) << "Unknown exception catched";
      }
    };
    if (pool_) {
      pool_->enqueue(op_run);
    } else {
      op_run();
    }
  }

  void GenerateVar(OpHandleBase *op_handle, const std::string &each_var_name,
Y
Yu Yang 已提交
118 119
                   const platform::Place &place, size_t place_offset) {
    auto &vars = vars_[place_offset][each_var_name];
Y
Yu Yang 已提交
120 121 122 123 124
    size_t version = vars.size();
    auto &var = vars[version];
    var.version_ = version;
    var.name_ = each_var_name;
    var.place_ = place;
Y
Yu Yang 已提交
125
    op_handle->AddOutput(&var);
Y
Yu Yang 已提交
126
  }
Y
Yu Yang 已提交
127 128
};

Y
Yu Yang 已提交
129
ParallelExecutor::ParallelExecutor(
Y
Yu Yang 已提交
130
    size_t num_threads, const std::vector<platform::Place> &places,
Y
Yu Yang 已提交
131 132 133
    const std::unordered_set<std::string> &params,
    const ProgramDesc &startup_program, const ProgramDesc &main_program,
    const std::string &loss_var_name, Scope *scope)
Y
Yu Yang 已提交
134
    : member_(new ParallelExecutorPrivate(num_threads, places)) {
Y
Yu Yang 已提交
135
  member_->global_scope_ = scope;
Y
Yu Yang 已提交
136

Y
Yu Yang 已提交
137 138 139 140
  // Step 1. RunStartupProgram and Bcast the params to devs.
  Executor exe(places[0]);
  exe.Run(startup_program, scope, 0);
  // Create local scopes
Y
Yu Yang 已提交
141 142
  for (size_t i = 0; i < member_->places_.size(); ++i) {
    member_->local_scopes_.push_back(&scope->NewScope());
Y
Yu Yang 已提交
143 144 145
  }

  // Bcast Parameters to all GPUs
Y
Yu Yang 已提交
146
  BuildNCCLCommunicator();
Y
Yu Yang 已提交
147
  if (platform::is_gpu_place(places[0]) &&
Y
Yu Yang 已提交
148 149
      member_->local_scopes_.size() != 1) {  // Is CUDA
    BCastParamsToGPUs(startup_program);
Y
Yu Yang 已提交
150 151 152 153 154 155
  }
  // Startup Program has been run. All local scopes has correct parameters.

  // Step 2. Convert main_program to SSA form and dependency graph. Also, insert
  // ncclOp
  ConstructDependencyGraph(params, main_program, loss_var_name);
Y
Yu Yang 已提交
156 157

  // Step 3. Create vars in each scope;
Y
Yu Yang 已提交
158
  for (auto *scope : member_->local_scopes_) {
Y
Yu Yang 已提交
159 160 161 162 163 164 165 166
    for (auto *var : main_program.Block(0).AllVars()) {
      if (scope->FindVar(var->Name()) != nullptr) {
        continue;
      }

      InitializeVariable(scope->Var(var->Name()), var->GetType());
    }
  }
Y
Yu Yang 已提交
167 168 169 170 171
}

void ParallelExecutor::ConstructDependencyGraph(
    const std::unordered_set<std::string> &params,
    const ProgramDesc &main_program, const std::string &loss_var_name) const {
Y
Yu Yang 已提交
172
  std::unordered_set<std::string> grads;
Y
Yu Yang 已提交
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
  for (auto &each_param : params) {
    grads.insert(each_param + "@GRAD");
  }

  bool is_forwarding = true;
  for (auto *op : main_program.Block(0).AllOps()) {
    bool change_forward = false;
    if (!is_forwarding) {
      // FIXME(yy): Do not hard code like this
      if (op->OutputArgumentNames().size() == 1 &&
          op->OutputArgumentNames()[0] == loss_var_name + "@GRAD") {
        continue;  // Drop fill 1. for backward coeff;
      }
    }

Y
Yu Yang 已提交
188 189 190 191 192
    for (size_t i = 0; i < member_->places_.size(); ++i) {
      auto &p = member_->places_[i];
      auto *s = member_->local_scopes_[i];

      member_->ops_.emplace_back(new ComputationOpHandle(*op, s, p));
Y
Yu Yang 已提交
193
      auto *op_handle = member_->ops_.back().get();
Y
Yu Yang 已提交
194 195
      op_handle->dev_ctx_[p] = const_cast<platform::DeviceContext *>(
          platform::DeviceContextPool::Instance().Get(p));
Y
Yu Yang 已提交
196 197 198 199

      auto var_names = op->InputArgumentNames();

      for (auto &each_var_name : var_names) {
Y
Yu Yang 已提交
200
        VarHandle *var = member_->GetVarHandle(each_var_name, p, i);
Y
Yu Yang 已提交
201
        op_handle->AddInput(var);
Y
Yu Yang 已提交
202 203 204 205
      }
      var_names = op->OutputArgumentNames();

      for (auto &each_var_name : var_names) {
Y
Yu Yang 已提交
206
        member_->GenerateVar(op_handle, each_var_name, p, i);
Y
Yu Yang 已提交
207 208 209 210 211
      }

      if (is_forwarding) {
        if (var_names.size() == 1 && var_names[0] == loss_var_name) {
          // Insert ScaleCost OpHandle
Y
Yu Yang 已提交
212 213 214 215
          op_handle =
              new ScaleLossGradOpHandle(this->member_->local_scopes_.size(), s,
                                        p, member_->nccl_ctxs_->DevCtx(p));
          member_->ops_.emplace_back(op_handle);
Y
Yu Yang 已提交
216

Y
Yu Yang 已提交
217 218 219 220 221 222
          // FIXME: Currently ScaleLossGradOp only use device_count as scale
          // factor. So it does not depend on any other operators.
          // VarHandle *loss = GetVarHandle(loss_var_name, place);
          // loss->pending_ops_.emplace_back(op_handle);
          // op_handle->inputs_.emplace_back(loss);

Y
Yu Yang 已提交
223
          member_->GenerateVar(op_handle, loss_var_name + "@GRAD", p, i);
Y
Yu Yang 已提交
224 225 226 227 228 229 230 231 232 233 234 235 236 237
          change_forward = true;
        }
      }
    }

    if (change_forward) {
      is_forwarding = false;
    }

    if (!is_forwarding) {
      auto var_names = op->OutputArgumentNames();
      for (auto &og : var_names) {
        if (grads.count(og) != 0) {  // is param grad
          // Insert NCCL AllReduce Op
Y
Yu Yang 已提交
238
          member_->ops_.emplace_back(new NCCLAllReduceOpHandle(
Y
Yu Yang 已提交
239
              member_->local_scopes_, member_->places_, *member_->nccl_ctxs_));
Y
Yu Yang 已提交
240 241
          auto *op_handle = member_->ops_.back().get();

Y
Yu Yang 已提交
242 243
          for (size_t i = 0; i < member_->places_.size(); ++i) {
            auto &p = member_->places_[i];
Y
Yu Yang 已提交
244
            auto &vars = member_->vars_[i][og];
Y
Yu Yang 已提交
245 246 247 248 249

            if (vars.empty()) {  // This device has no data. continue.
              continue;
            }
            auto *prev_grad = &vars[vars.size() - 1];
Y
Yu Yang 已提交
250 251
            op_handle->AddInput(prev_grad);

Y
Yu Yang 已提交
252
            auto &var = vars[vars.size()];
Y
Yu Yang 已提交
253
            var.place_ = p;
Y
Yu Yang 已提交
254 255
            var.name_ = og;
            var.version_ = vars.size() - 1;
Y
Yu Yang 已提交
256 257

            op_handle->AddOutput(&var);
Y
Yu Yang 已提交
258 259 260 261 262
          }
        }
      }
    }
  }
Y
Yu Yang 已提交
263

Y
Yu Yang 已提交
264 265 266
  /*
    Dependency graph has been constructed. However, there are still data
    harzaeds need to be handled.
Y
Yu Yang 已提交
267
   */
268
  PolishGraphToSupportDataHazards();
Y
Yu Yang 已提交
269
}
Y
Yu Yang 已提交
270

Y
Yu Yang 已提交
271 272 273 274 275 276 277
/**
 * We only handle write after read(WAR), since it should not have a write
 * after write in program. If there are write after write operators, we need
 * prune them.
 *
 * https://en.wikipedia.org/wiki/Hazard_(computer_architecture)#Write_after_read_(WAR)
 */
278
void ParallelExecutor::PolishGraphToSupportDataHazards() const {
Y
Yu Yang 已提交
279 280
  for (auto &var_map : member_->vars_) {
    for (auto &name_pair : var_map) {
Y
Yu Yang 已提交
281 282 283 284 285 286 287 288 289
      if (name_pair.second.size() <= 1) {
        return;
      }
      auto it_new = name_pair.second.rbegin();
      auto it_old = name_pair.second.rbegin();
      ++it_old;
      for (; it_old != name_pair.second.rend(); it_new = it_old, ++it_old) {
        auto *write_op = it_new->second.generated_op_;
        auto &read_ops = it_old->second.pending_ops_;
Y
Yu Yang 已提交
290 291 292 293 294 295
        auto *ex_write_op = it_old->second.generated_op_;

        if (ex_write_op == nullptr) {  // Nobody write this var.
          continue;
        }

Y
Yu Yang 已提交
296 297
        for (auto *read_op : read_ops) {
          // Manually add a dependency var from read_op to write_op;
Y
Yu Yang 已提交
298 299 300 301
          if (read_op == write_op) {
            // Read Write is the same op.
            continue;
          }
Y
Yu Yang 已提交
302

Y
Yu Yang 已提交
303
          auto *dep_var = new DummyVarHandle();
Y
Yu Yang 已提交
304 305
          read_op->AddOutput(dep_var);
          write_op->AddInput(dep_var);
Y
Yu Yang 已提交
306 307 308 309 310
          member_->dep_vars_.emplace(dep_var);
        }
      }
    }
  }
Y
Yu Yang 已提交
311 312 313 314
}

void ParallelExecutor::BCastParamsToGPUs(
    const ProgramDesc &startup_program) const {
Y
Yu Yang 已提交
315
#ifdef PADDLE_WITH_CUDA
Y
Yu Yang 已提交
316
  auto *main_scope = member_->local_scopes_[0];
Y
Yu Yang 已提交
317

Y
Yu Yang 已提交
318 319 320 321
  for (auto *var_desc : startup_program.Block(0).AllVars()) {
    if (var_desc->GetType() == proto::VarType::LOD_TENSOR) {
      auto &main_tensor =
          main_scope->FindVar(var_desc->Name())->Get<LoDTensor>();
Y
Yu Yang 已提交
322
      ncclDataType_t data_type = platform::ToNCCLDataType(main_tensor.type());
Y
Yu Yang 已提交
323 324 325
      auto &dims = main_tensor.dims();
      size_t numel = main_tensor.numel();

Y
Yu Yang 已提交
326
      platform::NCCLGroupGuard guard;
Y
Yu Yang 已提交
327

Y
Update  
Yu Yang 已提交
328 329 330 331 332 333
      for (size_t i = 0; i < member_->places_.size(); ++i) {
        auto place = member_->places_[i];
        void *buffer;
        if (i == 0) {
          buffer = const_cast<void *>(main_tensor.data<void>());
        } else {
Y
Yu Yang 已提交
334
          auto local_scope = member_->local_scopes_[i];
Y
Update  
Yu Yang 已提交
335 336 337 338 339
          auto *t = local_scope->Var(var_desc->Name())->GetMutable<LoDTensor>();
          t->Resize(dims);
          buffer = t->mutable_data(place, main_tensor.type());
        }

Y
Yu Yang 已提交
340
        auto &nccl_ctx = member_->nccl_ctxs_->at(place);
Y
Yu Yang 已提交
341 342
        platform::dynload::ncclBcast(buffer, numel, data_type, 0,
                                     nccl_ctx.comm_, nccl_ctx.stream());
Y
Yu Yang 已提交
343
      }
Y
Stash  
Yu Yang 已提交
344
    }
Y
Yu Yang 已提交
345
    member_->nccl_ctxs_->WaitAll();
Y
Stash  
Yu Yang 已提交
346
  }
Y
Yu Yang 已提交
347 348 349 350
#else
  PADDLE_THROW("Not compiled with CUDA");
#endif
}
Y
Yu Yang 已提交
351

Y
Yu Yang 已提交
352 353
void ParallelExecutor::BuildNCCLCommunicator() const {
#ifdef PADDLE_WITH_CUDA
Y
Yu Yang 已提交
354
  member_->nccl_ctxs_.reset(new platform::NCCLContextMap(member_->places_));
Y
Yu Yang 已提交
355
#endif
Y
Yu Yang 已提交
356 357
}

Y
Yu Yang 已提交
358 359
void ParallelExecutor::Run(const std::vector<std::string> &fetch_tensors,
                           const std::string &fetched_var_name) {
Y
Yu Yang 已提交
360
  bool use_event = true;
Y
Debug  
Yu Yang 已提交
361
  FeedFetchList fetched_data(fetch_tensors.size());
Y
Yu Yang 已提交
362
  // Version --> VarHandle
Y
Yu Yang 已提交
363
  member_->exception_.reset();
Y
Yu Yang 已提交
364
  std::unordered_map<VarHandleBase *, std::atomic<bool>> pending_vars;
Y
Yu Yang 已提交
365
  std::unordered_map<OpHandleBase *, size_t> pending_ops;
Y
Yu Yang 已提交
366
  std::vector<DummyVarHandle> dummy_vars;
Y
Yu Yang 已提交
367

Y
Yu Yang 已提交
368 369
  for (auto &var_map : member_->vars_) {
    for (auto &name_pair : var_map) {
Y
Yu Yang 已提交
370
      for (auto &version_pair : name_pair.second) {
Y
Yu Yang 已提交
371 372
        pending_vars[&version_pair.second] =
            version_pair.second.generated_op_ == nullptr;
Y
Yu Yang 已提交
373 374 375 376
      }
    }
  }

Y
Yu Yang 已提交
377
  for (auto &var : member_->dep_vars_) {
Y
Yu Yang 已提交
378
    pending_vars[var.get()] = var->generated_op_ == nullptr;
Y
Yu Yang 已提交
379 380
  }

Y
Yu Yang 已提交
381
  std::vector<OpHandleBase *> to_run;
Y
Yu Yang 已提交
382

Y
Yu Yang 已提交
383
  for (auto &op : member_->ops_) {
Y
Yu Yang 已提交
384 385 386 387 388 389 390
    if (op->inputs_.empty()) {  // Special case, Op has no input.
      to_run.emplace_back(op.get());
    } else {
      pending_ops.insert({op.get(), op->inputs_.size()});
    }
  }

Y
Yu Yang 已提交
391 392 393
  std::unordered_map<std::string, std::vector<VarHandleBase *>> fetched_vars;

  for (auto &fetch_var_name : fetch_tensors) {
Y
Yu Yang 已提交
394 395 396
    for (auto &var_map : member_->vars_) {
      auto it = var_map.find(fetch_var_name);
      if (it != var_map.end()) {
Y
Yu Yang 已提交
397 398 399 400 401 402 403 404 405 406
        fetched_vars[fetch_var_name].push_back(&it->second.rbegin()->second);
      }
    }
  }

  std::vector<FetchOpHandle> fetch_ops;

  for (size_t i = 0; i < fetch_tensors.size(); ++i) {
    auto &var_name = fetch_tensors[i];
    auto &vars = fetched_vars[var_name];
Y
Yu Yang 已提交
407
    fetch_ops.emplace_back(&fetched_data, i, &member_->local_scopes_);
Y
Yu Yang 已提交
408
    FetchOpHandle *op = &fetch_ops.back();
Y
Yu Yang 已提交
409 410

    // FIXME: Use new device context
Y
Yu Yang 已提交
411
    for (auto &p : member_->places_) {
Y
Yu Yang 已提交
412
      op->dev_ctx_[p] = member_->fetch_dev_ctxs_.Get(p);
Y
Yu Yang 已提交
413 414 415
    }

    for (auto *var : vars) {
Y
Yu Yang 已提交
416
      op->AddInput(var);
Y
Yu Yang 已提交
417
    }
Y
Yu Yang 已提交
418 419 420

    dummy_vars.emplace_back();
    auto *var = &dummy_vars.back();
Y
Yu Yang 已提交
421
    op->AddOutput(var);
Y
Yu Yang 已提交
422 423
    pending_vars[var] = false;

Y
Yu Yang 已提交
424 425 426
    pending_ops.insert({op, op->inputs_.size()});
  }

Y
Yu Yang 已提交
427
  for (auto *op : to_run) {
Y
Yu Yang 已提交
428
    member_->RunOp(use_event, pending_vars, op);
Y
Yu Yang 已提交
429 430
  }

Y
Yu Yang 已提交
431
  while (!pending_vars.empty()) {
Y
Yu Yang 已提交
432
    VarHandleBase *ready_var = nullptr;
Y
Yu Yang 已提交
433
    for (auto &pair : pending_vars) {
Y
Yu Yang 已提交
434
      if (pair.second.load(std::memory_order_acquire)) {
Y
Yu Yang 已提交
435
        ready_var = pair.first;
Y
Yu Yang 已提交
436 437
      }
    }
Y
Yu Yang 已提交
438
    if (ready_var == nullptr) {
Y
Yu Yang 已提交
439 440 441 442
      // FIXME use conditional var instead of busy wait.
      if (member_->exception_) {
        throw * member_->exception_;
      }
Y
Yu Yang 已提交
443
      continue;
Y
Yu Yang 已提交
444
    }
Y
Yu Yang 已提交
445
    pending_vars.erase(ready_var);
Y
Yu Yang 已提交
446
    to_run.clear();
Y
Yu Yang 已提交
447 448 449 450 451
    for (auto *op : ready_var->pending_ops_) {
      auto &deps = pending_ops[op];
      --deps;
      if (deps == 0) {
        to_run.emplace_back(op);
Y
Yu Yang 已提交
452 453 454 455
      }
    }
    for (auto *op : to_run) {
      pending_ops.erase(op);
Y
Yu Yang 已提交
456
      member_->RunOp(use_event, pending_vars, op);
Y
Yu Yang 已提交
457 458
    }
  }
Y
Yu Yang 已提交
459

Y
Debug  
Yu Yang 已提交
460 461 462 463 464 465
  for (auto &fetch_op : fetch_ops) {
    fetch_op.WaitAndMergeCPUTensors();
  }

  *member_->global_scope_->Var(fetched_var_name)->GetMutable<FeedFetchList>() =
      fetched_data;
Y
Yu Yang 已提交
466
}
Y
Yu Yang 已提交
467

Y
Yu Yang 已提交
468
}  // namespace framework
Y
Yang Yang 已提交
469
}  // namespace paddle