multi_devices_graph_builder.cc 28.3 KB
Newer Older
Y
Yu Yang 已提交
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.
C
chengduoZH 已提交
14
#include <algorithm>
Y
Yancey1989 已提交
15
#include <fstream>
C
chengduoZH 已提交
16
#include <string>
C
chengduoZH 已提交
17
#include <utility>
C
chengduoZH 已提交
18 19
#include <vector>

20
#include "paddle/fluid/framework/details/all_reduce_op_handle.h"
C
chengduoZH 已提交
21
#include "paddle/fluid/framework/details/broadcast_op_handle.h"
Y
Yu Yang 已提交
22
#include "paddle/fluid/framework/details/computation_op_handle.h"
23
#include "paddle/fluid/framework/details/data_balance_op_handle.h"
C
chengduoZH 已提交
24
#include "paddle/fluid/framework/details/multi_devices_graph_builder.h"
C
chengduoZH 已提交
25
#include "paddle/fluid/framework/details/reduce_op_handle.h"
Y
Yancey1989 已提交
26
#include "paddle/fluid/framework/details/rpc_op_handle.h"
Y
Yu Yang 已提交
27
#include "paddle/fluid/framework/details/scale_loss_grad_op_handle.h"
X
better  
Xin Pan 已提交
28
#include "paddle/fluid/framework/ir/graph_helper.h"
X
Xin Pan 已提交
29
#include "paddle/fluid/framework/ir/node.h"
Y
Fix bug  
yuyang18 已提交
30
#include "paddle/fluid/framework/op_info.h"
Y
Yu Yang 已提交
31
#include "paddle/fluid/framework/scope.h"
Y
Yu Yang 已提交
32

Y
Yu Yang 已提交
33 34 35
namespace paddle {
namespace framework {
namespace details {
Y
Yu Yang 已提交
36

X
Xin Pan 已提交
37
void MultiDevSSAGraphBuilder::Init() const {
X
Xin Pan 已提交
38 39 40 41
  loss_var_name_ = Get<const std::string>("loss_var_name");
  places_ = Get<const std::vector<platform::Place>>("places");
  local_scopes_ = Get<const std::vector<Scope *>>("local_scopes");
  strategy_ = Get<const BuildStrategy>("strategy");
Y
Yu Yang 已提交
42
#ifdef PADDLE_WITH_CUDA
X
Xin Pan 已提交
43
  nccl_ctxs_ = &Get<platform::NCCLContextMap>("nccl_ctxs");
Y
Yu Yang 已提交
44
#endif
X
Xin Pan 已提交
45

X
Xin Pan 已提交
46
  for (auto &p : Get<const std::unordered_set<std::string>>("params")) {
Y
Yu Yang 已提交
47 48
    grad_names_.insert(GradVarName(p));
  }
Y
Yancey1989 已提交
49
  balance_vars_.resize(places_.size(), 0);
Y
yuyang18 已提交
50 51 52 53 54
  if (strategy_.enable_data_balance_ && places_.size() == 1) {
    LOG(WARNING) << "It is no need to enable data balance when there is only "
                    "one place. enable_data_balance is set to False.";
    strategy_.enable_data_balance_ = false;
  }
Y
Yu Yang 已提交
55 56
}

X
Xin Pan 已提交
57 58
void MultiDevSSAGraphBuilder::CreateOpHandleIOs(ir::Graph *result,
                                                ir::Node *node,
Y
Yu Yang 已提交
59 60
                                                size_t place_id) const {
  auto p = places_[place_id];
X
Xin Pan 已提交
61
  auto *op_handle = result->Get<GraphOps>("ops").back().get();
X
Xin Pan 已提交
62 63
  op_handle->SetDeviceContext(p,
                              platform::DeviceContextPool::Instance().Get(p));
T
wip  
typhoonzero 已提交
64

65 66
  for (ir::Node *input : node->inputs) {
    VarHandle *var = CreateOrGetLatestVarHandle(result, input, p, place_id);
T
wip  
typhoonzero 已提交
67 68 69
    op_handle->AddInput(var);
  }

70
  for (ir::Node *output : node->outputs) {
X
polish  
Xin Pan 已提交
71 72 73 74 75 76 77 78
    ir::Node *new_node = nullptr;
    if (output->Var()) {
      new_node = result->CreateVarNode(output->Var());
    } else {
      new_node =
          result->CreateEmptyNode(output->Name(), ir::Node::Type::kVariable);
    }
    CreateOpOutput(result, op_handle, new_node, p, place_id);
T
wip  
typhoonzero 已提交
79 80
  }
}
Y
fix pe  
Yancey1989 已提交
81 82

std::vector<std::string> MultiDevSSAGraphBuilder::FindDistTrainSendVars(
X
Xin Pan 已提交
83
    const std::vector<ir::Node *> &nodes) const {
Y
fix pe  
Yancey1989 已提交
84
  std::vector<std::string> send_vars;
Y
Yancey1989 已提交
85 86
  // since parameters are all in block 0,
  // it's enough to only scan send ops in block 0
87 88
  for (auto &node : nodes) {
    OpDesc *op = node->Op();
Y
Yancey1989 已提交
89 90
    // TODO(Yancey1989): use a graceful method to find send op,
    // instead of the the hard code string
91
    if (op->Type() == "send") {
Y
fix pe  
Yancey1989 已提交
92 93 94 95 96 97 98 99 100 101
      auto op_vars = op->InputArgumentNames();
      send_vars.reserve(send_vars.size() +
                        std::distance(op_vars.begin(), op_vars.end()));
      send_vars.insert(send_vars.end(), op_vars.begin(), op_vars.end());
    }
  }
  return send_vars;
}

std::vector<std::string> MultiDevSSAGraphBuilder::FindDistTrainRecvVars(
X
Xin Pan 已提交
102
    const std::vector<ir::Node *> &nodes) const {
Y
fix pe  
Yancey1989 已提交
103
  std::vector<std::string> recv_vars;
104 105
  for (auto &node : nodes) {
    OpDesc *op = node->Op();
Y
Yancey1989 已提交
106 107 108
    // TODO(Yancey1989): use a graceful method to find recv op,
    // instead of the hard code string
    if (op->Type() == "recv") {
Y
fix pe  
Yancey1989 已提交
109 110 111 112 113 114 115 116 117 118
      auto op_vars = op->OutputArgumentNames();
      recv_vars.reserve(recv_vars.size() +
                        std::distance(op_vars.begin(), op_vars.end()));
      recv_vars.insert(recv_vars.end(), op_vars.begin(), op_vars.end());
    }
  }
  return recv_vars;
}

bool MultiDevSSAGraphBuilder::IsDistTrainOp(
119
    ir::Node *node, const std::vector<std::string> &send_vars,
Y
fix pe  
Yancey1989 已提交
120 121
    const std::vector<std::string> &recv_vars) const {
  if (send_vars.size() == 0 || recv_vars.size() == 0) {
T
typhoonzero 已提交
122 123 124
    return false;
  }

Y
Yu Yang 已提交
125 126 127 128
  /**
   * Check any of opvars contains `.block` and in sendvars
   */
  auto checker = [](const std::vector<std::string> &opvars,
Y
fix pe  
Yancey1989 已提交
129
                    const std::vector<std::string> &rpc_vars) -> bool {
T
typhoonzero 已提交
130
    for (auto &var : opvars) {
Y
Yancey1989 已提交
131 132 133
      // a variable name with the suffix `.block` means it's a splited
      // variable by (DistributeTranspiler)
      // [python/paddle/fluid/transpiler/distribute_transpiler.py]
T
typhoonzero 已提交
134
      if (var.find(".block") != std::string::npos &&
Y
fix pe  
Yancey1989 已提交
135
          std::find(rpc_vars.begin(), rpc_vars.end(), var) != rpc_vars.end()) {
Y
Yu Yang 已提交
136
        return true;
T
typhoonzero 已提交
137 138
      }
    }
Y
Yu Yang 已提交
139
    return false;
T
typhoonzero 已提交
140 141
  };

142 143 144
  std::vector<std::string> input_var_names;
  std::vector<std::string> output_var_names;
  for (ir::Node *input : node->inputs) {
X
Xin Pan 已提交
145
    input_var_names.push_back(input->Name());
146 147
  }
  for (ir::Node *output : node->outputs) {
X
Xin Pan 已提交
148
    output_var_names.push_back(output->Name());
149 150 151 152
  }

  return checker(output_var_names, send_vars) ||
         checker(input_var_names, recv_vars);
T
typhoonzero 已提交
153 154
}

Y
Yancey1989 已提交
155 156 157 158
size_t MultiDevSSAGraphBuilder::GetAppropriateDeviceID(
    const std::vector<std::string> &var_names) const {
  int64_t numel_sum = 0;
  for (auto var_name : var_names) {
X
Xin Pan 已提交
159
    if (all_vars_.find(var_name) == all_vars_.end()) continue;
Y
Yancey1989 已提交
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
    auto var_desc = all_vars_.at(var_name);
    PADDLE_ENFORCE_NOT_NULL(var_desc);
    auto dim = framework::make_ddim(var_desc->GetShape());
    int64_t numel = framework::product(dim);
    PADDLE_ENFORCE_GT(numel, 0);
    numel_sum += numel;
  }

  auto smallest =
      std::min_element(std::begin(balance_vars_), std::end(balance_vars_));
  size_t dev_id =
      static_cast<size_t>(std::distance(std::begin(balance_vars_), smallest));
  balance_vars_[dev_id] += numel_sum;
  return dev_id;
}

X
better  
Xin Pan 已提交
176 177 178 179 180
// Topology sort the graph nodes from inputs to outputs.
// Since SSAGraphBuilder depends on forward/backward nodes to assign devices
// to parameter/gradients before optimizer ops, topo sort is insufficient. (
// some optimizer ops might not depend on any nodes), we manually move all
// optimizer nodes after last backward nodes.
X
Xin Pan 已提交
181 182 183
// However, the assumption by SSAGraphBuilder should be relaxed in the future.
std::vector<ir::Node *> SortOpsAndDelayOptimizeOp(const ir::Graph &graph) {
  std::vector<ir::Node *> ret = ir::TopologySortOperations(graph);
X
better  
Xin Pan 已提交
184 185 186 187 188
  size_t last_backward = 0;
  for (size_t i = 0; i < ret.size(); ++i) {
    if (boost::get<int>(
            ret[i]->Op()->GetAttr(OpProtoAndCheckerMaker::OpRoleAttrName())) ==
        static_cast<int>(OpRole::kBackward)) {
X
Xin Pan 已提交
189
      last_backward = i;
X
better  
Xin Pan 已提交
190 191 192
    }
  }

X
Xin Pan 已提交
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
  std::vector<ir::Node *> optimize_ops;
  std::vector<ir::Node *> sorted_ret;
  for (size_t i = 0; i < ret.size(); ++i) {
    if (i < last_backward) {
      if (boost::get<int>(ret[i]->Op()->GetAttr(
              OpProtoAndCheckerMaker::OpRoleAttrName())) ==
          static_cast<int>(OpRole::kOptimize)) {
        optimize_ops.push_back(ret[i]);
      } else {
        sorted_ret.push_back(ret[i]);
      }
    } else if (i == last_backward) {
      sorted_ret.push_back(ret[i]);
      // Verify that no operations before optimize ops depends on optimize ops.
      std::unordered_set<ir::Node *> optimize_set(optimize_ops.begin(),
                                                  optimize_ops.end());
      for (ir::Node *n : sorted_ret) {
        for (ir::Node *in : n->inputs) {
          for (ir::Node *pre_n : in->inputs) {
            PADDLE_ENFORCE(optimize_set.find(pre_n) == optimize_set.end(),
                           "optimize operations cannot be depended by forward "
                           "or backward node %s -> %s",
                           pre_n->Name(), n->Name());
          }
        }
X
Xin Pan 已提交
218
      }
X
Xin Pan 已提交
219 220 221 222
      sorted_ret.insert(sorted_ret.end(), optimize_ops.begin(),
                        optimize_ops.end());
    } else {
      sorted_ret.push_back(ret[i]);
X
Xin Pan 已提交
223 224
    }
  }
X
better  
Xin Pan 已提交
225 226 227
  return sorted_ret;
}

X
Xin Pan 已提交
228 229
std::unique_ptr<ir::Graph> MultiDevSSAGraphBuilder::Apply(
    std::unique_ptr<ir::Graph> graph) const {
X
Xin Pan 已提交
230
  Init();
X
Xin Pan 已提交
231
  // Give the topology sort order and rebuild the graph structure.
X
better  
Xin Pan 已提交
232
  std::vector<ir::Node *> sorted_ops = SortOpsAndDelayOptimizeOp(*graph);
X
Xin Pan 已提交
233 234
  auto nodes = graph->ReleaseNodes();
  ir::Graph &result = *graph;
235 236

  for (auto &node : nodes) {
X
Xin Pan 已提交
237
    if (node->NodeType() == ir::Node::Type::kVariable && node->Var()) {
X
Xin Pan 已提交
238
      all_vars_.emplace(node->Name(), node->Var());
239
    }
C
fix ci  
chengduoZH 已提交
240
  }
C
chengduoZH 已提交
241
  std::unordered_set<std::string> og_has_been_broadcast;
Y
Yu Yang 已提交
242 243

  // We cannot invoke resize. It is a bug of GCC 4.8
X
Xin Pan 已提交
244 245 246
  result.Set("vars", new GraphVars(places_.size()));
  result.Set("dep_vars", new GraphDepVars);
  result.Set("ops", new GraphOps);
X
Xin Pan 已提交
247
  result.Set("sharded_var_device", new ShardedVarDevice);
248

Y
fix pe  
Yancey1989 已提交
249 250
  // find send/recv vars so that we can place the distributed training
  // realted op in the place 0
X
Xin Pan 已提交
251 252
  auto send_vars = FindDistTrainSendVars(sorted_ops);
  auto recv_vars = FindDistTrainRecvVars(sorted_ops);
T
typhoonzero 已提交
253

C
chengduoZH 已提交
254 255 256
  std::vector<std::unordered_set<std::string>> bcast_var_name_set;
  bcast_var_name_set.resize(places_.size());

C
chengduoZH 已提交
257
  size_t cur_device_id = 0;
Y
Yu Yang 已提交
258
  bool is_forwarding = true;
259

X
better  
Xin Pan 已提交
260
  for (ir::Node *node : sorted_ops) {
Y
Yancey1989 已提交
261
    if (boost::get<int>(
262
            node->Op()->GetAttr(OpProtoAndCheckerMaker::OpRoleAttrName())) ==
Y
Yancey1989 已提交
263
        static_cast<int>(OpRole::kRPC)) {
X
Xin Pan 已提交
264 265 266 267
      CreateRPCOp(&result, node);
    } else if (IsDistTrainOp(node, send_vars, recv_vars)) {
      CreateDistTrainOp(&result, node);
    } else if (IsScaleLossOp(node)) {
Y
Yu Yang 已提交
268
      // user can customize loss@grad if not use_default_grad_scale_
Y
yuyang18 已提交
269 270
      if (strategy_.gradient_scale_ !=
          BuildStrategy::GradientScaleStrategy::kCustomized) {
X
Xin Pan 已提交
271
        // TODO(paddle-dev): Why is there no input for this op_handle?
Y
Yu Yang 已提交
272 273
        CreateScaleLossGradOp(&result);
      }
274 275 276 277
      // This assumes the backward generating code will ensure IsScaleLossOp
      // is true only for the op that scale the final scalar loss.
      // It also assumes backward op will always follow the forward op in
      // the block.
Y
Yu Yang 已提交
278
      is_forwarding = false;
Y
Yu Yang 已提交
279
    } else {
X
Xin Pan 已提交
280
      int op_dev_id = GetOpDeviceID(result, node);
C
chengduo 已提交
281
      if (op_dev_id != -1) {  // This op only runs on one specific device.
X
Xin Pan 已提交
282
        CreateComputationalOp(&result, node, op_dev_id);
283
        for (ir::Node *n : node->outputs) {
X
Xin Pan 已提交
284 285
          graph->Get<ShardedVarDevice>("sharded_var_device")
              .emplace(n->Name(), op_dev_id);
C
chengduoZH 已提交
286
        }
C
chengduo 已提交
287 288 289
      } else {
        // This op runs on all devices, and its output may have parameter's
        // gradients.
X
Xin Pan 已提交
290
        // TODO(paddle-dev): Why is so special about "read" op?
291 292
        if (node->Op()->Type() == "read" && strategy_.enable_data_balance_) {
          node->Op()->SetAttr("throw_eof_exp", false);
X
Xin Pan 已提交
293
          CreateComputationalOps(&result, node, places_.size());
294
          const auto &data_var_names = node->Op()->Output("Out");
295
          InsertDataBalanceOp(&result, data_var_names);
F
fengjiayi 已提交
296
        } else {
X
Xin Pan 已提交
297
          CreateComputationalOps(&result, node, places_.size());
298 299
        }

C
chengduo 已提交
300 301 302
        if (!is_forwarding && places_.size() > 1) {
          // Currently, we assume that once gradient is generated, it can be
          // broadcast, and each gradient is only broadcast once.
303
          if (static_cast<bool>(boost::get<int>(node->Op()->GetAttr(
C
chengduo 已提交
304 305 306
                                    OpProtoAndCheckerMaker::OpRoleAttrName())) &
                                static_cast<int>(OpRole::kBackward))) {
            try {
307 308
              auto backward_vars = boost::get<std::vector<std::string>>(
                  node->Op()->GetNullableAttr(
C
chengduo 已提交
309
                      OpProtoAndCheckerMaker::OpRoleVarAttrName()));
Y
yuyang18 已提交
310

C
chengduo 已提交
311
              PADDLE_ENFORCE_EQ(backward_vars.size() % 2, 0);
Y
yuyang18 已提交
312

C
chengduo 已提交
313 314 315 316
              for (size_t i = 0; i < backward_vars.size(); i += 2) {
                auto &p_name = backward_vars[i];
                auto &g_name = backward_vars[i + 1];
                VLOG(10) << "Bcast " << g_name << " for parameter " << p_name;
Y
yuyang18 已提交
317

C
chengduo 已提交
318 319 320 321
                switch (strategy_.reduce_) {
                  case BuildStrategy::ReduceStrategy::kReduce:
                    cur_device_id = GetAppropriateDeviceID({g_name});
                    CreateReduceOp(&result, g_name, cur_device_id);
X
Xin Pan 已提交
322 323
                    graph->Get<ShardedVarDevice>("sharded_var_device")
                        .emplace(g_name, cur_device_id);
C
chengduo 已提交
324 325 326 327 328 329 330 331 332 333 334 335 336 337
                    bcast_var_name_set[cur_device_id].emplace(p_name);
                    break;
                  case BuildStrategy::ReduceStrategy::kAllReduce:
                    if (IsSparseGradient(g_name)) {
                      CreateReduceOp(&result, g_name, 0);
                      CreateBroadcastOp(&result, g_name, 0);
                    } else {
                      InsertAllReduceOp(&result, g_name);
                    }
                    break;
                  default:
                    LOG(FATAL) << "Unknown reduce strategy ";
                    break;
                }
Y
yuyang18 已提交
338
              }
C
chengduo 已提交
339
            } catch (boost::bad_get e) {
C
chengduoZH 已提交
340
            }
Y
Yu Yang 已提交
341 342 343 344 345 346
          }
        }
      }
    }
  }

347 348 349 350 351 352 353 354 355 356 357 358 359
  bool use_gpu = false;
#ifdef PADDLE_WITH_CUDA
  use_gpu = nccl_ctxs_ != nullptr;
#endif

  if (use_gpu ||
      strategy_.reduce_ == BuildStrategy::ReduceStrategy::kAllReduce) {
    // Insert BCast Ops
    for (size_t dev_id = 0; dev_id < bcast_var_name_set.size(); ++dev_id) {
      auto &to_bcast_set = bcast_var_name_set[dev_id];
      for (auto &bcast_name : to_bcast_set) {
        CreateBroadcastOp(&result, bcast_name, dev_id);
      }
C
chengduoZH 已提交
360 361
    }
  }
X
Xin Pan 已提交
362 363 364 365 366
  /*
  Dependency graph has been constructed. However, there are still data
  hazards need to be handled.
 */
  PolishGraphToSupportDataHazards(&result);
367

Y
Yu Yang 已提交
368 369 370 371
  /*
   * Only variables should be the leaves of graph.
   */
  AddOutputToLeafOps(&result);
X
Xin Pan 已提交
372
  PADDLE_ENFORCE(!ir::HasCircle(result));
Q
qiaolongfei 已提交
373
  return graph;
Y
Yu Yang 已提交
374 375
}

Y
Yancey1989 已提交
376 377 378
bool MultiDevSSAGraphBuilder::IsSparseGradient(const std::string &og) const {
  PADDLE_ENFORCE(all_vars_.count(og) != 0);
  if (all_vars_.at(og)->GetType() == proto::VarType::SELECTED_ROWS) {
C
fix ci  
chengduoZH 已提交
379 380 381
    return true;
  }
  return false;
382 383
}

384 385 386 387 388 389 390 391 392 393 394 395 396
void MultiDevSSAGraphBuilder::SetCommunicationContext(
    OpHandleBase *op_handle, const platform::Place &p) const {
#ifdef PADDLE_WITH_CUDA
  if (nccl_ctxs_ == nullptr) {
    op_handle->SetDeviceContext(p,
                                platform::DeviceContextPool::Instance().Get(p));
  }
#else
  op_handle->SetDeviceContext(p,
                              platform::DeviceContextPool::Instance().Get(p));
#endif
}

X
Xin Pan 已提交
397
void MultiDevSSAGraphBuilder::CreateBroadcastOp(ir::Graph *result,
C
chengduoZH 已提交
398
                                                const std::string &p_name,
C
chengduoZH 已提交
399
                                                size_t src_dev_id) const {
C
chengduoZH 已提交
400
#ifdef PADDLE_WITH_CUDA
X
polish  
Xin Pan 已提交
401 402 403
  auto *op_handle = new BroadcastOpHandle(
      result->CreateEmptyNode("broadcast", ir::Node::Type::kOperation),
      local_scopes_, places_, nccl_ctxs_);
C
chengduoZH 已提交
404
#else
X
polish  
Xin Pan 已提交
405 406 407
  auto *op_handle = new BroadcastOpHandle(
      result->CreateEmptyNode("broadcast", ir::Node::Type::kOperation),
      local_scopes_, places_);
C
chengduoZH 已提交
408
#endif
X
Xin Pan 已提交
409
  result->Get<GraphOps>("ops").emplace_back(op_handle);
X
Xin Pan 已提交
410

X
Xin Pan 已提交
411 412
  auto *in =
      result->Get<GraphVars>("vars").at(src_dev_id).at(p_name).back().get();
C
chengduoZH 已提交
413 414 415 416
  op_handle->AddInput(in);

  for (size_t i = 0; i < places_.size(); ++i) {
    auto &p = places_[i];
C
chengduoZH 已提交
417
    SetCommunicationContext(op_handle, p);
X
Xin Pan 已提交
418
    auto &vars = result->Get<GraphVars>("vars").at(i).at(p_name);
X
polish  
Xin Pan 已提交
419 420 421
    auto *out_var = new VarHandle(
        result->CreateEmptyNode(p_name, ir::Node::Type::kVariable), vars.size(),
        i, p_name, p);
C
chengduoZH 已提交
422 423 424 425 426
    vars.emplace_back(out_var);
    op_handle->AddOutput(out_var);
  }
}

X
Xin Pan 已提交
427
void MultiDevSSAGraphBuilder::CreateComputationalOp(ir::Graph *result,
428
                                                    ir::Node *node,
C
chengduoZH 已提交
429
                                                    int dev_id) const {
430
  result->Get<GraphOps>("ops").emplace_back(
X
Xin Pan 已提交
431
      new ComputationOpHandle(result->CreateOpNode(node->Op()),
432 433
                              local_scopes_[dev_id], places_[dev_id]));
  CreateOpHandleIOs(result, node, dev_id);
C
chengduoZH 已提交
434 435
}

X
Xin Pan 已提交
436
void MultiDevSSAGraphBuilder::InsertAllReduceOp(ir::Graph *result,
C
chengduoZH 已提交
437
                                                const std::string &og) const {
Y
Yu Yang 已提交
438
#ifdef PADDLE_WITH_CUDA
X
polish  
Xin Pan 已提交
439 440 441
  result->Get<GraphOps>("ops").emplace_back(new AllReduceOpHandle(
      result->CreateEmptyNode("allreduce", ir::Node::Type::kOperation),
      local_scopes_, places_, nccl_ctxs_));
C
chengduoZH 已提交
442
#else
X
Xin Pan 已提交
443
  result->Get<GraphOps>("ops").emplace_back(new AllReduceOpHandle(
X
polish  
Xin Pan 已提交
444 445
      result->CreateEmptyNode("allreduce", ir::Node::Type::kOperation),
      local_scopes_, places_));
C
chengduoZH 已提交
446
#endif
X
Xin Pan 已提交
447
  auto *op_handle = result->Get<GraphOps>("ops").back().get();
Y
Yu Yang 已提交
448 449 450

  for (size_t i = 0; i < places_.size(); ++i) {
    auto &p = places_[i];
C
chengduoZH 已提交
451
    SetCommunicationContext(op_handle, p);
X
Xin Pan 已提交
452
    auto &vars = result->Get<GraphVars>("vars")[i][og];
Y
Yu Yang 已提交
453 454
    PADDLE_ENFORCE(!vars.empty());
    auto &prev_grad = vars.back();
Y
Yu Yang 已提交
455 456
    op_handle->AddInput(prev_grad.get());

X
Xin Pan 已提交
457
    auto var =
X
polish  
Xin Pan 已提交
458 459
        new VarHandle(result->CreateEmptyNode(og, ir::Node::Type::kVariable),
                      vars.size(), i, og, p);
Y
Yu Yang 已提交
460 461 462 463 464
    vars.emplace_back(var);
    op_handle->AddOutput(var);
  }
}

465
void MultiDevSSAGraphBuilder::InsertDataBalanceOp(
X
Xin Pan 已提交
466
    ir::Graph *result, const std::vector<std::string> &datas) const {
F
fengjiayi 已提交
467
#ifdef PADDLE_WITH_CUDA
X
polish  
Xin Pan 已提交
468 469 470
  result->Get<GraphOps>("ops").emplace_back(new DataBalanceOpHandle(
      result->CreateEmptyNode("data_balance", ir::Node::Type::kOperation),
      local_scopes_, places_, nccl_ctxs_));
F
fengjiayi 已提交
471
#else
X
Xin Pan 已提交
472
  result->Get<GraphOps>("ops").emplace_back(new DataBalanceOpHandle(
X
polish  
Xin Pan 已提交
473 474
      result->CreateEmptyNode("data_balance", ir::Node::Type::kOperation),
      local_scopes_, places_));
F
fengjiayi 已提交
475
#endif
X
Xin Pan 已提交
476
  auto *op_handle = result->Get<GraphOps>("ops").back().get();
477 478 479 480
  for (size_t i = 0; i < places_.size(); ++i) {
    auto &p = places_[i];
    SetCommunicationContext(op_handle, p);
    for (const std::string &d_name : datas) {
X
Xin Pan 已提交
481
      auto &vars = result->Get<GraphVars>("vars")[i][d_name];
482 483
      PADDLE_ENFORCE(!vars.empty());
      op_handle->AddInput(vars.back().get());
X
polish  
Xin Pan 已提交
484 485 486
      auto var = new VarHandle(
          result->CreateEmptyNode(d_name, ir::Node::Type::kVariable),
          vars.size(), i, d_name, p);
487 488 489 490 491 492
      vars.emplace_back(var);
      op_handle->AddOutput(var);
    }
  }
}

Y
Yu Yang 已提交
493 494 495 496 497 498 499 500 501 502 503 504
bool MultiDevSSAGraphBuilder::IsParameterGradientOnce(
    const std::string &og,
    std::unordered_set<std::string> *og_has_been_broadcast) const {
  bool is_pg_once =
      grad_names_.count(og) != 0 && og_has_been_broadcast->count(og) == 0;
  if (is_pg_once) {
    // Insert NCCL AllReduce Op
    og_has_been_broadcast->insert(og);
  }
  return is_pg_once;
}

X
Xin Pan 已提交
505 506
int MultiDevSSAGraphBuilder::GetOpDeviceID(const ir::Graph &graph,
                                           ir::Node *node) const {
Y
yuyang18 已提交
507
  if (strategy_.reduce_ != BuildStrategy::ReduceStrategy::kReduce) {
C
chengduoZH 已提交
508 509
    return -1;
  }
510
  int op_role = boost::get<int>(
511
      node->Op()->GetAttr(framework::OpProtoAndCheckerMaker::OpRoleAttrName()));
512 513
  if (op_role != static_cast<int>(framework::OpRole::kOptimize)) {
    return -1;
C
chengduoZH 已提交
514
  }
515
  auto param_grad = boost::get<std::vector<std::string>>(
X
Xin Pan 已提交
516
      node->Op()->GetAttr(OpProtoAndCheckerMaker::OpRoleVarAttrName()));
517 518

  PADDLE_ENFORCE_EQ(param_grad.size(), 2U);
X
Xin Pan 已提交
519
  int dev_id = GetVarDeviceID(graph, param_grad[1]);
X
Xin Pan 已提交
520 521
  PADDLE_ENFORCE_NE(dev_id, -1, "dev_id should not be -1.[%s, %s, %s]",
                    node->Op()->Type(), param_grad[0], param_grad[1]);
522
  return dev_id;
523 524
}

X
Xin Pan 已提交
525 526 527 528 529
int MultiDevSSAGraphBuilder::GetVarDeviceID(const ir::Graph &graph,
                                            const std::string &varname) const {
  auto &sharded_var_device = graph.Get<ShardedVarDevice>("sharded_var_device");
  auto got = sharded_var_device.find(varname);
  return got == sharded_var_device.end() ? -1 : got->second;
C
chengduoZH 已提交
530 531
}

X
Xin Pan 已提交
532
void MultiDevSSAGraphBuilder::CreateScaleLossGradOp(ir::Graph *result) const {
Y
Yu Yang 已提交
533 534 535
  for (size_t i = 0; i < places_.size(); ++i) {
// Insert ScaleCost OpHandle
#ifdef PADDLE_WITH_CUDA
C
chengduoZH 已提交
536 537 538
    auto *communication_dev_ctx =
        nccl_ctxs_ ? nccl_ctxs_->DevCtx(places_[i])
                   : platform::DeviceContextPool::Instance().Get(places_[i]);
Y
Yu Yang 已提交
539 540 541 542
#else
    auto *communication_dev_ctx =
        platform::DeviceContextPool::Instance().Get(platform::CPUPlace());
#endif
X
Xin Pan 已提交
543
    auto *op_handle = new ScaleLossGradOpHandle(
X
polish  
Xin Pan 已提交
544 545 546
        result->CreateEmptyNode("scale_loss_grad", ir::Node::Type::kOperation),
        local_scopes_.size(), local_scopes_[i], places_[i],
        communication_dev_ctx);
X
Xin Pan 已提交
547
    result->Get<GraphOps>("ops").emplace_back(op_handle);
Y
Yu Yang 已提交
548 549 550 551 552 553 554

    // 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);

X
polish  
Xin Pan 已提交
555 556 557 558
    CreateOpOutput(result, op_handle,
                   result->CreateEmptyNode(GradVarName(loss_var_name_),
                                           ir::Node::Type::kVariable),
                   places_[i], i);
Y
Yu Yang 已提交
559 560 561
  }
}

X
Xin Pan 已提交
562
void MultiDevSSAGraphBuilder::CreateComputationalOps(ir::Graph *result,
563
                                                     ir::Node *node,
T
typhoonzero 已提交
564 565
                                                     size_t num_places) const {
  for (size_t scope_idx = 0; scope_idx < num_places; ++scope_idx) {
Y
Yu Yang 已提交
566 567
    auto p = places_[scope_idx];
    auto s = local_scopes_[scope_idx];
X
Xin Pan 已提交
568 569
    result->Get<GraphOps>("ops").emplace_back(
        new ComputationOpHandle(result->CreateOpNode(node->Op()), s, p));
570
    CreateOpHandleIOs(result, node, scope_idx);
Y
Yu Yang 已提交
571 572 573
  }
}

X
Xin Pan 已提交
574
VarHandle *MultiDevSSAGraphBuilder::CreateReduceOp(ir::Graph *result,
C
chengduoZH 已提交
575 576
                                                   const std::string &og,
                                                   int dst_dev_id) const {
C
chengduoZH 已提交
577
#ifdef PADDLE_WITH_CUDA
X
Xin Pan 已提交
578
  result->Get<GraphOps>("ops").emplace_back(new ReduceOpHandle(
X
polish  
Xin Pan 已提交
579 580
      result->CreateEmptyNode("reduce", ir::Node::Type::kOperation),
      local_scopes_, places_, nccl_ctxs_));
C
chengduoZH 已提交
581
#else
582
  result->Get<GraphOps>("ops").emplace_back(new ReduceOpHandle(
X
polish  
Xin Pan 已提交
583 584
      result->CreateEmptyNode("reduce", ir::Node::Type::kOperation),
      local_scopes_, places_));
C
chengduoZH 已提交
585
#endif
X
Xin Pan 已提交
586
  auto *op_handle = result->Get<GraphOps>("ops").back().get();
C
chengduoZH 已提交
587 588 589

  for (size_t i = 0; i < places_.size(); ++i) {
    auto &p = places_[i];
C
chengduoZH 已提交
590
    SetCommunicationContext(op_handle, p);
X
Xin Pan 已提交
591
    auto &vars = result->Get<GraphVars>("vars")[i][og];
C
chengduoZH 已提交
592 593 594 595
    PADDLE_ENFORCE(!vars.empty());
    auto &prev_grad = vars.back();
    op_handle->AddInput(prev_grad.get());
  }
X
Xin Pan 已提交
596
  auto &vars = result->Get<GraphVars>("vars")[dst_dev_id][og];
X
polish  
Xin Pan 已提交
597 598 599
  auto var =
      new VarHandle(result->CreateEmptyNode(og, ir::Node::Type::kVariable),
                    vars.size(), dst_dev_id, og, places_[dst_dev_id]);
C
chengduoZH 已提交
600 601 602 603 604
  vars.emplace_back(var);
  op_handle->AddOutput(var);
  return var;
}

605 606
// Find the first occurence of `prev_op_name` and make current `op` depend
// on it.
X
Xin Pan 已提交
607
void MultiDevSSAGraphBuilder::ConnectOp(ir::Graph *result, OpHandleBase *op,
Y
fix pe  
Yancey1989 已提交
608
                                        const std::string &prev_op_name) const {
X
Xin Pan 已提交
609
  for (auto &prev_op : result->Get<GraphOps>("ops")) {
Y
fix pe  
Yancey1989 已提交
610
    if (prev_op->Name() == prev_op_name) {
X
Xin Pan 已提交
611
      auto *dep_var = new DummyVarHandle(result->CreateControlDepVar());
Y
Yancey1989 已提交
612
      prev_op->AddOutput(dep_var);
X
Xin Pan 已提交
613
      result->Get<GraphDepVars>("dep_vars").emplace(dep_var);
Y
fix pe  
Yancey1989 已提交
614
      op->AddInput(dep_var);
Y
Yancey1989 已提交
615 616 617 618
    }
  }
}

X
Xin Pan 已提交
619
void MultiDevSSAGraphBuilder::CreateDistTrainOp(ir::Graph *result,
620
                                                ir::Node *node) const {
Y
Yancey1989 已提交
621
  int op_dev_id = -1;
622 623 624
  std::vector<std::string> input_var_names;
  std::vector<std::string> output_var_names;
  for (ir::Node *input : node->inputs) {
X
Xin Pan 已提交
625
    input_var_names.push_back(input->Name());
626 627
  }
  for (ir::Node *output : node->outputs) {
X
Xin Pan 已提交
628
    output_var_names.push_back(output->Name());
629 630 631 632
  }

  if (node->Op()->Type() == "split_byref" ||
      node->Op()->Type() == "split_selected_rows") {
X
Xin Pan 已提交
633
    // TODO(paddle-dev): getting the first var is not safe.
X
Xin Pan 已提交
634
    op_dev_id = GetVarDeviceID(*result, input_var_names[0]);
Y
Yancey1989 已提交
635
    if (strategy_.reduce_ == BuildStrategy::ReduceStrategy::kAllReduce) {
636 637
      op_dev_id = GetAppropriateDeviceID(input_var_names);
      for (auto &varname : input_var_names) {
X
Xin Pan 已提交
638 639
        result->Get<ShardedVarDevice>("sharded_var_device")
            .emplace(varname, op_dev_id);
Y
Yancey1989 已提交
640 641
      }
    }
642
    for (auto &varname : output_var_names) {
X
Xin Pan 已提交
643 644
      result->Get<ShardedVarDevice>("sharded_var_device")
          .emplace(varname, op_dev_id);
Y
Yancey1989 已提交
645
    }
646
  } else if (node->Op()->Type() == "concat") {
X
Xin Pan 已提交
647
    op_dev_id = GetVarDeviceID(*result, input_var_names[0]);
648
    for (auto &varname : output_var_names) {
X
Xin Pan 已提交
649 650
      result->Get<ShardedVarDevice>("sharded_var_device")
          .emplace(varname, op_dev_id);
Y
yi.wu 已提交
651
    }
Y
Yancey1989 已提交
652 653 654 655 656 657 658
  } else {
    PADDLE_ENFORCE(
        "the distribute training related op should be in [split_byref, "
        "concat].");
  }

  PADDLE_ENFORCE(op_dev_id != -1,
659 660
                 "can not find right place for distributed op: %s",
                 node->Op()->Type());
Y
Yancey1989 已提交
661

662 663
  CreateComputationalOp(result, node, op_dev_id);
  if (node->Op()->Type() == "concat") {
X
Xin Pan 已提交
664
    ConnectOp(result, result->Get<GraphOps>("ops").back().get(),
X
Xin Pan 已提交
665
              "fetch_barrier");
Y
Yancey1989 已提交
666 667 668
  }
}

669
// Create RPC related op handles that connects its in ops and out ops.
X
Xin Pan 已提交
670 671
void MultiDevSSAGraphBuilder::CreateRPCOp(ir::Graph *result,
                                          ir::Node *node) const {
Y
Yancey1989 已提交
672
  int op_dev_id = -1;
673
  if (node->Op()->Type() == "send") {
X
Xin Pan 已提交
674
    // TODO(paddle-dev): getting the first var is not safe.
X
Xin Pan 已提交
675
    op_dev_id = GetVarDeviceID(*result, node->inputs[0]->Name());
X
Xin Pan 已提交
676 677
    PADDLE_ENFORCE(!ir::IsControlDepVar(*node->inputs[0]),
                   "This hack no longer holds, please fix.");
Y
Yancey1989 已提交
678 679
    // the variable name which contains .block means it was splited by
    // split_byref op
680 681
    // so that we can balance the variable blocks to all the pserver
    // instances.
Y
Yancey1989 已提交
682
    if (strategy_.reduce_ == BuildStrategy::ReduceStrategy::kAllReduce &&
X
Xin Pan 已提交
683
        node->inputs[0]->Name().find(".block") == std::string::npos) {
684 685
      std::vector<std::string> input_var_names;
      for (ir::Node *n : node->inputs) {
X
Xin Pan 已提交
686
        input_var_names.push_back(n->Name());
687 688 689
      }
      op_dev_id = GetAppropriateDeviceID(input_var_names);
      for (auto &varname : input_var_names) {
X
Xin Pan 已提交
690 691
        result->Get<ShardedVarDevice>("sharded_var_device")
            .emplace(varname, op_dev_id);
Y
Yancey1989 已提交
692 693
      }
    }
694 695 696
  } else if (node->Op()->Type() == "recv") {
    std::vector<std::string> output_var_names;
    for (ir::Node *n : node->outputs) {
X
Xin Pan 已提交
697
      output_var_names.push_back(n->Name());
698 699 700
    }
    op_dev_id = GetAppropriateDeviceID(output_var_names);
    for (auto &varname : output_var_names) {
X
Xin Pan 已提交
701 702
      result->Get<ShardedVarDevice>("sharded_var_device")
          .emplace(varname, op_dev_id);
Y
Yancey1989 已提交
703 704 705 706 707 708 709
    }
  } else {
    // send_barrier and fetch_barrier op can be scheduled on device 0
    op_dev_id = 0;
  }

  PADDLE_ENFORCE(op_dev_id != -1, "can not find the right place for rpc op: %s",
710
                 node->Op()->Type());
Y
Yancey1989 已提交
711

712 713 714
  result->Get<GraphOps>("ops").emplace_back(new RPCOpHandle(
      result->CreateOpNode(node->Op()), *node->Op(), local_scopes_[op_dev_id],
      node->Op()->Type(), places_[op_dev_id]));
Y
fix pe  
Yancey1989 已提交
715

716
  if (node->Op()->Type() == "send_barrier") {
X
Xin Pan 已提交
717
    ConnectOp(result, result->Get<GraphOps>("ops").back().get(), "send");
718
  } else if (node->Op()->Type() == "recv") {
X
Xin Pan 已提交
719
    ConnectOp(result, result->Get<GraphOps>("ops").back().get(),
X
Xin Pan 已提交
720
              "send_barrier");
721
  } else if (node->Op()->Type() == "fetch_barrier") {
X
Xin Pan 已提交
722
    ConnectOp(result, result->Get<GraphOps>("ops").back().get(), "recv");
723
  } else if (node->Op()->Type() == "send") {
Y
Yancey1989 已提交
724 725 726
    // do nothing
  } else {
    PADDLE_THROW(
Y
Yancey1989 已提交
727
        "rpc op should be in ["
728
        "send, send_barrier. recv, fetch_barrier]");
Y
Yancey1989 已提交
729 730
  }

731
  CreateOpHandleIOs(result, node, op_dev_id);
Y
Yu Yang 已提交
732 733
}

734
bool MultiDevSSAGraphBuilder::IsScaleLossOp(ir::Node *node) const {
Y
yuyang18 已提交
735
  return boost::get<int>(
736
             node->Op()->GetAttr(OpProtoAndCheckerMaker::OpRoleAttrName())) ==
Y
Fix bug  
yuyang18 已提交
737 738 739
             (static_cast<int>(OpRole::kBackward) |
              static_cast<int>(OpRole::kLoss)) &&
         !loss_var_name_.empty();  // If loss_var is empty. This is test mode
Y
Yu Yang 已提交
740
}
Y
Yu Yang 已提交
741 742 743
}  // namespace details
}  // namespace framework
}  // namespace paddle
X
Xin Pan 已提交
744 745 746

REGISTER_PASS(multi_device_pass,
              paddle::framework::details::MultiDevSSAGraphBuilder);