/* 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. */ #include #include #include "paddle/common/flags.h" #include "paddle/fluid/framework/barrier.h" #include "paddle/fluid/framework/convert_utils.h" #include "paddle/fluid/framework/data_type.h" #include "paddle/fluid/framework/device_worker.h" #include "paddle/fluid/framework/new_executor/interpreter/dependency_builder.h" #include "paddle/fluid/operators/controlflow/conditional_block_op_helper.h" #include "paddle/fluid/operators/isfinite_op.h" #include "paddle/fluid/platform/densetensor_printer.h" #include "paddle/phi/common/reduce_type.h" #include "paddle/phi/core/distributed/comm_context_manager.h" #include "paddle/phi/core/platform/cpu_helper.h" #include "paddle/phi/kernels/funcs/math_function.h" #if defined(PADDLE_WITH_NCCL) || defined(PADDLE_WITH_RCCL) #include "paddle/phi/core/distributed/nccl_comm_context.h" #endif #if defined(PADDLE_WITH_GLOO) #include "paddle/fluid/framework/fleet/gloo_wrapper.h" #endif #include "paddle/fluid/framework/data_type_transform.h" #include "paddle/fluid/framework/program_utils.h" #include "paddle/utils/string/string_helper.h" COMMON_DECLARE_bool(enable_exit_when_partial_worker); COMMON_DECLARE_int32(enable_adjust_op_order); PHI_DEFINE_EXPORTED_bool(gpugraph_force_device_batch_num_equal, false, "enable force_device_batch_num_equal, default false"); COMMON_DECLARE_bool(enable_dump_main_program); PHI_DEFINE_EXPORTED_int32(gpugraph_offload_param_stat, 0, "enable offload param stat, default 0"); PHI_DEFINE_EXPORTED_string(gpugraph_offload_param_extends, ".w_0_moment,.b_0_moment", "offload param extends list"); PHI_DEFINE_EXPORTED_int32(gpugraph_offload_gather_copy_maxsize, 16, "offload gather copy max size , default 16M"); PHI_DEFINE_EXPORTED_int32(gpugraph_parallel_copyer_split_maxsize, 64, "offload gather copy max size , default 64M"); PHI_DEFINE_EXPORTED_int32(gpugraph_parallel_stream_num, 8, "offload parallel copy stream num"); PHI_DEFINE_EXPORTED_bool(gpugraph_enable_print_op_debug, false, "enable print op debug ,default false"); namespace paddle::framework { std::atomic HogwildWorker::quit_flag_(false); Barrier g_barrier; #if defined(PADDLE_WITH_CUDA) class GPUParallelCopyer { public: GPUParallelCopyer(const phi::gpuStream_t &stream, const int device_id, const int stream_num) : dev_stream_(stream), device_id_(device_id), max_stream_(stream_num), streams_(stream_num) { platform::CUDADeviceGuard guard(device_id_); for (size_t i = 0; i < max_stream_; ++i) { PADDLE_ENFORCE_GPU_SUCCESS(cudaStreamCreate(&streams_[i])); } } ~GPUParallelCopyer() { platform::CUDADeviceGuard guard(device_id_); for (size_t i = 0; i < max_stream_; ++i) { PADDLE_WARN_GPU_SUCCESS(cudaStreamDestroy(streams_[i])); } } void Copy(const DenseTensor &src_tensor, const Place &dest_place, DenseTensor *dest_tensor) { size_t mem_len = src_tensor.memory_size(); if (!dest_tensor->IsInitialized()) { dest_tensor->Resize(src_tensor.dims()); dest_tensor->set_layout(src_tensor.layout()); } const char *src_ptr = (const char *)src_tensor.data(); char *dest_ptr = reinterpret_cast( dest_tensor->mutable_data(dest_place, src_tensor.dtype(), mem_len)); if (copy_count_ == 0) { platform::GpuStreamSync(dev_stream_); } size_t pos = 0; auto &src_place = src_tensor.place(); while (pos < mem_len) { size_t data_len = mem_len - pos; if (data_len > split_max_len_) { data_len = split_max_len_; } auto &cur_stream = streams_[copy_count_ % max_stream_]; const char *src = src_ptr + pos; char *dst = dest_ptr + pos; memory::Copy(dest_place, dst, src_place, src, data_len, cur_stream); pos = pos + split_max_len_; ++copy_count_; } } void Wait() { if (copy_count_ == 0) { return; } if (copy_count_ > max_stream_) { for (auto &ss : streams_) { platform::GpuStreamSync(ss); } } else { for (size_t i = 0; i < copy_count_; ++i) { platform::GpuStreamSync(streams_[i]); } } copy_count_ = 0; } void SyncDevStream() { platform::GpuStreamSync(dev_stream_); } private: phi::gpuStream_t dev_stream_ = nullptr; int device_id_ = -1; size_t max_stream_ = 0; std::vector streams_; size_t copy_count_ = 0; size_t split_max_len_ = FLAGS_gpugraph_parallel_copyer_split_maxsize * 1024 * 1024; }; #endif template inline void Tensor2Pinned(DenseTensor *tensor, const TStream &stream) { #if defined(PADDLE_WITH_CUDA) const size_t mem_len = tensor->memory_size(); auto place = GPUPinnedPlace(); auto holder = memory::AllocShared(place, mem_len); memory::Copy( place, holder->ptr(), tensor->place(), tensor->data(), mem_len, stream); tensor->ResetHolderWithType(holder, tensor->dtype()); #endif } template void HogwildWorker::OffLoadVarInfo::CopyInputs(const Scope *root, const Place &place, Scope *scope, TCopyer *copyer) { if (!cast_vars.empty()) { for (auto &obj : cast_vars) { auto src_var = root->FindLocalVar(obj.second); PADDLE_ENFORCE_NE( src_var, nullptr, common::errors::NotFound("root scope not found var name=%s", obj.second.c_str())); auto &src_tensor = src_var->Get(); auto dest_var = scope->FindLocalVar(obj.first); PADDLE_ENFORCE_NE(dest_var, nullptr, common::errors::NotFound("dest name=%s is nullptr", obj.first.c_str())); auto *dest_tensor = dest_var->GetMutable(); auto dtype = framework::TransToProtoVarType(dest_tensor->dtype()); framework::TransDataType(src_tensor, dtype, dest_tensor); } } #if defined(PADDLE_WITH_CUDA) if (copy_vars.empty()) { return; } for (auto &name : copy_vars) { auto src_var = root->FindLocalVar(name); PADDLE_ENFORCE_NE(src_var, nullptr, common::errors::NotFound( "root scope not found var name=%s", name.c_str())); auto &src_tensor = src_var->Get(); auto dest_var = scope->FindLocalVar(name); PADDLE_ENFORCE_NE( dest_var, nullptr, common::errors::NotFound("dest name=%s is nullptr", name.c_str())); auto *dest_tensor = dest_var->GetMutable(); copyer->Copy(src_tensor, place, dest_tensor); } copyer->Wait(); #endif } template void HogwildWorker::OffLoadVarInfo::BackUpInputs(Scope *root_scope, Scope *scope, TCopyer *copyer) { #if defined(PADDLE_WITH_CUDA) if (backup_vars.empty()) { return; } for (auto &name : backup_vars) { auto var = scope->FindLocalVar(name); if (var == nullptr) { continue; } auto src_tensor = var->Get(); auto root_var = root_scope->FindLocalVar(name); if (root_var == nullptr) { root_var = root_scope->Var(name); auto root_tensor = root_var->GetMutable(); auto place = GPUPinnedPlace(); copyer->Copy(src_tensor, place, root_tensor); } else { auto root_tensor = root_var->GetMutable(); if (root_tensor->IsInitialized() && !phi::is_gpu_place(root_tensor->place())) { copyer->Copy(src_tensor, root_tensor->place(), root_tensor); } } } copyer->Wait(); #endif } void HogwildWorker::Initialize(const TrainerDesc &desc) { fetch_config_ = desc.fetch_config(); param_ = desc.hogwild_param(); skip_ops_.resize(param_.skip_ops_size()); for (int i = 0; i < param_.skip_ops_size(); ++i) { skip_ops_[i] = param_.skip_ops(i); } use_cvm_ = desc.use_cvm(); thread_barrier_ = desc.thread_barrier(); use_ps_gpu_ = desc.use_ps_gpu(); use_gpu_graph_ = desc.use_gpu_graph(); for (int i = 0; i < param_.stat_var_names_size(); ++i) { std::string name = param_.stat_var_names(i); stat_var_name_map_[name] = 1; skip_vars_.push_back(name); } is_offload_communication_ = (FLAGS_gpugraph_offload_param_stat & 0x01); is_offload_param_ = (FLAGS_gpugraph_offload_param_stat & 0x02); // split extends offload_exts_ = paddle::string::split_string(FLAGS_gpugraph_offload_param_extends, ","); if (is_offload_param_ && !offload_exts_.empty()) { VLOG(0) << "need offload extends=" << paddle::string::join_strings(offload_exts_, ","); } } int HogwildWorker::IsParameter(const std::string &name, bool full_match) { return -1; } void HogwildWorker::BuildShardingDepends(const ProgramDesc &program) { nccl_rank_id_ = static_cast(static_cast(place_.GetDeviceId())); auto &block = program.Block(0); auto all_desc = block.AllOps(); bool is_has_sync_comm_stream = false; for (auto &op_desc : all_desc) { // broadcast op if (op_desc->Type() != "c_broadcast") { // has sync comm stream if (op_desc->Type() == "c_sync_comm_stream") { is_has_sync_comm_stream = true; } continue; } int root_id = op_desc->GetAttrIfExists("root"); int ring_id = op_desc->GetAttrIfExists("ring_id"); if (ring_id >= 0 && ring_id != ring_id_) { ring_id_ = ring_id; } std::string new_name; for (auto &o : op_desc->Inputs()) { for (auto &name : o.second) { // amp size_t pos = name.find(".cast_fp16"); if (pos != std::string::npos) { new_name = name.substr(0, pos); cast_fp16_vars_.insert(std::make_pair(name, new_name)); param_cast_vars_.insert(std::make_pair(new_name, name)); if (root_id == nccl_rank_id_) { need_cast_vars_.insert(std::make_pair(name, new_name)); } } else { new_name = name; } auto var = block.FindVar(new_name); if (!var->Persistable() || !var->IsParameter()) { continue; } if (params2rootid_.find(new_name) != params2rootid_.end()) { continue; } params2rootid_.insert(std::make_pair(new_name, root_id)); } } } // adjust op order need sync comm stream op enable_adjust_op_order_ = (is_has_sync_comm_stream && FLAGS_enable_adjust_op_order); if (params2rootid_.empty()) { return; } sharding_mode_ = true; // check find for (auto &var : block.AllVars()) { if (!var->Persistable()) { continue; } int ret = IsParameter(var->Name(), var->IsParameter()); if (ret < 0 || ret == 1) { if (ret == 1) { persist_param_vars_.insert(var->Name()); } continue; } if (var->IsParameter()) { unpersist_vars_.insert(var->Name()); } else { remove_vars_.insert(var->Name()); } } int total_broadcast = 0; int remove_broadcast = 0; int remove_sync_stream = 0; int remove_cast_op = 0; std::multiset param2refs; std::multiset out2refs; for (auto &op_desc : all_desc) { bool find = false; if (op_desc->Type() == "c_sync_calc_stream") { // remove error sync auto &inputs = op_desc->Input("X"); std::vector removenames; for (auto &name : inputs) { auto it = out2refs.find(name); if (it != out2refs.end()) { removenames.push_back(name); continue; } find = true; ++remove_sync_stream; break; } if (!removenames.empty()) { for (auto &name : removenames) { auto it = out2refs.find(name); if (it == out2refs.end()) { continue; } out2refs.erase(it); } } } else if (!param_cast_vars_.empty() && op_desc->Type() == "cast") { // AMP auto &inputs = op_desc->Input("X"); for (auto &name : inputs) { auto it = param_cast_vars_.find(name); if (it == param_cast_vars_.end()) { break; } find = true; ++remove_cast_op; break; } } else if (is_offload_communication_ && op_desc->Type() == "c_broadcast") { ++total_broadcast; // single node p2p copy if (!is_multi_node_ && cast_fp16_vars_.empty()) { find = true; ++remove_broadcast; } else { auto &inputs = op_desc->Input("X"); for (auto &name : inputs) { if (cast_fp16_vars_.find(name) != cast_fp16_vars_.end()) { break; } if (param2refs.find(name) != param2refs.end()) { find = true; continue; } param2refs.insert(name); } if (find) { ++remove_broadcast; } } } else { for (auto &o : op_desc->Inputs()) { for (auto &name : o.second) { if (remove_vars_.find(name) == remove_vars_.end()) { continue; } find = true; break; } if (find) { break; } } } if (find) { remove_ops_.insert(op_desc); } else { for (auto &o : op_desc->Outputs()) { for (auto &name : o.second) { out2refs.insert(name); } } } } // add offload if (is_offload_communication_) { for (auto &it : params2rootid_) { if (it.second == nccl_rank_id_) { continue; } if (param_cast_vars_.find(it.first) != param_cast_vars_.end()) { continue; } offload_names_.insert(it.first); } } // reset dump param if (need_dump_param_ && dump_param_ != nullptr) { for (auto &name : *dump_param_) { auto var = block.FindVar(name); if (var == nullptr) { continue; } std::string new_name = name; size_t pos = new_name.find("@"); if (pos != std::string::npos) { new_name = name.substr(0, pos); } if (persist_param_vars_.find(new_name) == persist_param_vars_.end()) { continue; } shard_dump_params_.push_back(name); } dump_param_ = &shard_dump_params_; } // reset dump fields if (need_dump_field_ && dump_fields_ != nullptr) { for (auto &name : *dump_fields_) { auto var = block.FindVar(name); if (var == nullptr) { continue; } if (remove_vars_.find(name) != remove_vars_.end()) { continue; } shard_dump_fields_.push_back(name); } dump_fields_ = &shard_dump_fields_; } VLOG(0) << "device id=" << int(place_.GetDeviceId()) << ", nccl rank=" << nccl_rank_id_ << ", total param count=" << params2rootid_.size() << ", remove op count=" << remove_ops_.size() << ", remove var count=" << remove_vars_.size() << ", unpersist var count=" << unpersist_vars_.size() << ", persist var count=" << persist_param_vars_.size() << ", dump param count=" << shard_dump_params_.size() << ", dump fields count=" << shard_dump_fields_.size() << ", offload var name count=" << offload_names_.size() << ", total_broadcast=" << total_broadcast << ", remove_broadcast=" << remove_broadcast << ", remove c_sync_calc_stream=" << remove_sync_stream << ", remove cast_op=" << remove_cast_op; } size_t HogwildWorker::AdjustOffloadOps(const ProgramDesc &program) { // offload size_t offload_cnt = 0; if (offload_names_.empty()) { return offload_cnt; } // offload adam std::multiset param2refs; for (size_t op_id = 0; op_id < ops_.size(); ++op_id) { auto &op = ops_[op_id]; if (op->Type() == "c_broadcast") { continue; } // offload int cnt = 0; bool is_first = false; for (auto &o : op->Inputs()) { for (auto &name : o.second) { if (offload_names_.find(name) == offload_names_.end()) { continue; } auto dest_var = thread_scope_->Var(name); // init local var PADDLE_ENFORCE_NE(dest_var, nullptr, common::errors::InvalidArgument( "init var error name=%s", name.c_str())); offload_vars_[op.get()].copy_vars.push_back(name); // nccl broadcast param if (is_offload_communication_) { if (param2refs.find(name) == param2refs.end()) { param2refs.insert(name); is_first = true; } } ++cnt; } } offload_cnt += cnt; if (cnt > 0) { int op_role = op->Attr("op_role"); auto &op_offload = offload_vars_[op.get()]; // add gc auto it = unused_vars_.find(op.get()); if (it != unused_vars_.end()) { for (auto &name : op_offload.copy_vars) { if (std::find(it->second.begin(), it->second.end(), name) != it->second.end()) { continue; } it->second.push_back(name); } } else { unused_vars_.insert(std::make_pair(op.get(), op_offload.copy_vars)); } if (is_first) { // first used single node used p2p copy, multi node used nccl broadcast if (is_multi_node_) { op_offload.backup_vars = std::move(op_offload.copy_vars); op_offload.copy_vars.clear(); } } else { // offload adam need backup param to pinned memory if (op_role == static_cast(OpRole::kOptimize)) { for (auto &name : op_offload.copy_vars) { auto it = params2rootid_.find(name); if (it != params2rootid_.end() && it->second != nccl_rank_id_) { continue; } // only copy adam status op_offload.backup_vars.push_back(name); } } } } } // if not need gather if (FLAGS_gpugraph_offload_gather_copy_maxsize <= 0) { return offload_cnt; } // gather copy inputs const int64_t max_gather_len = FLAGS_gpugraph_offload_gather_copy_maxsize * 1024 * 1024; std::vector recycle_ops; std::multimap name2refs; auto &block = program.Block(0); // get param length auto get_length_func = [&block](const std::vector &vars, std::vector *out_vars) { int64_t total_len = 0; for (auto &name : vars) { if (out_vars != nullptr) { auto it = std::find(out_vars->begin(), out_vars->end(), name); if (it != out_vars->end()) { continue; } out_vars->push_back(name); } auto desc = block.FindVar(name); int64_t len = 1; for (auto &num : desc->GetShape()) { len = len * num; } total_len += len; } return total_len; }; // check vars gc auto add_gc_refs_func = [this, &name2refs](const OperatorBase *op) { auto it = unused_vars_.find(op); if (it == unused_vars_.end()) { return; } for (auto &name : it->second) { if (offload_names_.find(name) == offload_names_.end()) { continue; } auto itx = name2refs.find(name); if (itx == name2refs.end()) { name2refs.insert(std::make_pair(name, 1)); } else { ++itx->second; } } }; auto remove_gc_vars_func = [this, &name2refs](const size_t &start_idx, const size_t &end_idx) { for (size_t op_idx = start_idx; op_idx < end_idx; ++op_idx) { auto &op = ops_[op_idx]; auto it = unused_vars_.find(op.get()); if (it == unused_vars_.end()) { continue; } std::vector new_vars; for (auto &name : it->second) { auto itx = name2refs.find(name); if (itx == name2refs.end()) { new_vars.push_back(name); continue; } if (--itx->second == 0) { new_vars.push_back(name); } } it->second = new_vars; } }; size_t op_idx = 0; if (is_multi_node_) { while (op_idx < ops_.size()) { int op_role = ops_[op_idx]->Attr("op_role"); if (op_role == static_cast(OpRole::kBackward)) { break; } ++op_idx; } } size_t start_op_idx = 0; int64_t total_len = 0; std::vector *out_vars = nullptr; while (op_idx < ops_.size()) { auto op = ops_[op_idx].get(); auto it = offload_vars_.find(op); if (it == offload_vars_.end()) { if (out_vars != nullptr) { add_gc_refs_func(op); } ++op_idx; continue; } // add self length if (out_vars == nullptr) { start_op_idx = op_idx; total_len = get_length_func(it->second.copy_vars, nullptr); out_vars = &it->second.copy_vars; } else { total_len += get_length_func(it->second.copy_vars, out_vars); it->second.copy_vars.clear(); if (it->second.copy_vars.empty() && it->second.backup_vars.empty() && it->second.cast_vars.empty()) { recycle_ops.push_back(it->first); } } add_gc_refs_func(op); // max length reset if (total_len > max_gather_len) { out_vars = nullptr; // remove gc vars names remove_gc_vars_func(start_op_idx, op_idx + 1); } ++op_idx; } // remove last gc vars names if (out_vars != nullptr && start_op_idx < op_idx) { remove_gc_vars_func(start_op_idx, op_idx); } // erase empty offload ops for (auto &op : recycle_ops) { offload_vars_.erase(op); } VLOG(0) << "device id=" << thread_id_ << ", gather offload ops size=" << offload_vars_.size() << ", recycle size=" << recycle_ops.size(); return offload_cnt; } void HogwildWorker::CreateThreadOperators(const ProgramDesc &program) { auto &block = program.Block(0); op_names_.clear(); auto all_desc = block.AllOps(); std::set remove_ids; size_t op_index = 0; for (auto &op_desc : all_desc) { // skip feed fetch op std::string op_name = op_desc->Type(); if (op_name == "feed" || op_name == "fetch") { for (auto &o : op_desc->Inputs()) { skip_vars_.insert(skip_vars_.end(), o.second.begin(), o.second.end()); } for (auto &o : op_desc->Outputs()) { skip_vars_.insert(skip_vars_.end(), o.second.begin(), o.second.end()); } } bool need_skip = false; for (auto t = 0u; t < skip_ops_.size(); ++t) { if (op_desc->Type().find(skip_ops_[t]) != std::string::npos) { need_skip = true; break; } } if (need_skip) { continue; } // skip remove ops, remove sync if (remove_ops_.find(op_desc) != remove_ops_.end() || op_name == "c_sync_comm_stream") { if (enable_adjust_op_order_) { remove_ids.insert(op_index); } else { continue; } } op_names_.push_back(op_name); ops_.emplace_back(OpRegistry::CreateOp(*op_desc)); // change to device stream if (op_name == "c_broadcast" || op_name == "c_allreduce_sum" || (op_name == "all_reduce" && op_desc->GetAttrIfExists("reduce_type") == static_cast(phi::ReduceType::kRedSum)) || (op_name == "reduce" && op_desc->GetAttrIfExists("reduce_type") == static_cast(phi::ReduceType::kRedSum))) { ops_[op_index]->SetAttr("use_calc_stream", true); } op_index++; } if (enable_adjust_op_order_) { std::vector new_order; size_t start_index = 0; for (auto &op : ops_) { int op_role = op->Attr("op_role"); if ((op_role == static_cast(OpRole::kForward)) || (op_role == (static_cast(OpRole::kForward) | static_cast(OpRole::kLoss))) || (op_role == static_cast(OpRole::kLRSched))) { start_index++; } else { break; } } if (start_index < ops_.size()) { platform::Timer tm; tm.Start(); interpreter::DependencyBuilderSimplify depend_builder; // depend_builder.Build(ops_, start_index, sharding_mode_); hbm not safe // should run in debug model need to fix depend_builder.Build(ops_, start_index, false); new_order = depend_builder.get_new_executor_order(); std::vector> new_ops; std::vector final_order; std::vector new_op_names; for (auto &index : new_order) { if (remove_ids.count(index) == 0) { new_ops.push_back(std::move(ops_[index])); final_order.push_back(index); new_op_names.push_back(op_names_[index]); } } ops_ = std::move(new_ops); op_names_ = std::move(new_op_names); tm.Pause(); // add log VLOG(0) << "device id=" << thread_id_ << ", total op size=" << all_desc.size() << ", remove op size=" << remove_ids.size() << ", adjust op size=" << new_order.size() << ", final op size=" << final_order.size() << ", span time=" << tm.ElapsedSec() << "sec"; } } operators::PrepareSafeEagerDeletionOnConditionalOpAndConditionalGradOp( program, 0, ops_); // not need gc int64_t max_memory_size = GetEagerDeletionThreshold(); if (max_memory_size < 0) { return; } // skip dump fields if (need_dump_field_ && dump_fields_ != nullptr) { skip_vars_.insert( skip_vars_.end(), dump_fields_->begin(), dump_fields_->end()); } // skip dump params if (need_dump_param_ && dump_param_ != nullptr) { skip_vars_.insert( skip_vars_.end(), dump_param_->begin(), dump_param_->end()); } int fetch_var_num = fetch_config_.fetch_var_names_size(); if (fetch_var_num > 0) { for (int i = 0; i < fetch_var_num; ++i) { std::string name = fetch_config_.fetch_var_names(i); skip_vars_.push_back(name); } } unused_vars_ = GetUnusedVars(block, ops_, skip_vars_, &unpersist_vars_, sharding_mode_); // adjust offload ops size_t offload_cnt = AdjustOffloadOps(program); // add cast ops size_t cast_cnt = 0; if (!need_cast_vars_.empty()) { for (size_t op_id = 0; op_id < ops_.size(); ++op_id) { auto &op = ops_[op_id]; if (op->Type() != "c_broadcast") { continue; } for (auto &o : op->Inputs()) { for (auto &name : o.second) { auto it = need_cast_vars_.find(name); if (it == need_cast_vars_.end()) { continue; } ++cast_cnt; offload_vars_[op.get()].cast_vars.push_back( std::make_pair(it->first, it->second)); } } } } // debug str if (FLAGS_enable_dump_main_program) { std::ostringstream str_os; for (auto &op : ops_) { str_os << op->DebugStringEx(thread_scope_); // add gc auto it = unused_vars_.find(op.get()); if (it != unused_vars_.end()) { str_os << ", gc names: ["; for (auto &name : it->second) { str_os << name << ","; } str_os << "]"; } // add offload auto itx = offload_vars_.find(op.get()); if (itx != offload_vars_.end()) { str_os << ", offload copies: ["; for (auto &name : itx->second.copy_vars) { str_os << name << ","; } str_os << "], backups: ["; for (auto &name : itx->second.backup_vars) { str_os << name << ","; } str_os << "]"; if (!itx->second.cast_vars.empty()) { str_os << ", casts:["; for (auto &obj : itx->second.cast_vars) { str_os << obj.second << "->" << obj.first << ","; } str_os << "]"; } } str_os << "\n"; } std::string filename = "./device_"; filename += std::to_string(thread_id_); filename += "_ops.txt"; WriteToFile(filename.c_str(), str_os.str()); } // debug VLOG(0) << "device id=" << thread_id_ << ", total op count=" << all_desc.size() << ", create op count=" << ops_.size() << ", skip vars count=" << skip_vars_.size() << ", unused vars op count=" << unused_vars_.size() << ", offload op count=" << offload_vars_.size() << ", offload input count=" << offload_cnt << ", cast count=" << cast_cnt; } inline void PrintTensor(const std::string &name, const std::string &info, Scope *scope) { std::stringstream ss; platform::PrintVar(scope, name, info, &ss); std::cout << ss.str() << std::endl; } bool HogwildWorker::IsNeedOffload(const std::string &name) { if (!is_offload_param_) { return false; } if (offload_exts_.empty()) { return false; } for (auto &ext : offload_exts_) { if (name.find(ext) == std::string::npos) { continue; } return true; } return false; } void HogwildWorker::CreateThreadScope(const ProgramDesc &program) { auto &block = program.Block(0); PADDLE_ENFORCE_NOT_NULL( root_scope_, common::errors::NotFound( "Root scope should be set before creating thread scope.")); thread_scope_ = &root_scope_->NewScope(); int persist_total = 0; int persist_param = 0; int persist_share = 0; int persist_reset = 0; int pinned_param = 0; int resize_var_cnt = 0; int fp16_param = 0; std::vector del_var_names; for (auto &var : block.AllVars()) { auto name = var->Name(); if (remove_vars_.find(name) != remove_vars_.end()) { if (free_param_vars_.find(name) != free_param_vars_.end()) { del_var_names.push_back(name); VLOG(1) << "remove need delete var name=" << name; } continue; } all_param_.push_back(name); if (var->Persistable()) { ++persist_total; if (stat_var_name_map_.find(name) != stat_var_name_map_.end()) { Variable *root_var = root_scope_->FindVar(name); PADDLE_ENFORCE_NOT_NULL( root_var, common::errors::NotFound("Root scope should contain variable.")); auto root_tensor = root_var->Get(); if (root_tensor.place() == place_) { continue; } auto *ptr1 = thread_scope_->Var(name); InitializeVariable(ptr1, var->GetType()); DenseTensor *thread_tensor = ptr1->GetMutable(); #define MemsetCallback(cpp_type, proto_type) \ do { \ if (framework::TransToProtoVarType(root_tensor.dtype()) == proto_type) { \ SetZero(thread_tensor, root_tensor); \ } \ } while (0) _ForEachDataType_(MemsetCallback); } #if defined(PADDLE_WITH_HETERPS) && defined(PADDLE_WITH_PSCORE) else if (unpersist_vars_.find(name) == unpersist_vars_.end()) { // NOLINT if (use_gpu_graph_ && use_ps_gpu_) { Variable *root_var = root_scope_->FindVar(name); if (!root_var) { VLOG(0) << "not found var name=" << name; continue; } if (root_var->IsType()) { continue; } ++persist_param; DenseTensor *root_tensor = root_var->GetMutable(); auto var_dtype = phi::TransToPhiDataType(static_cast(var->GetDataType())); if (root_tensor->dtype() != var_dtype) { DenseTensor tmp_tensor; tmp_tensor.Resize(root_tensor->dims()); tmp_tensor.set_layout(root_tensor->layout()); tmp_tensor.mutable_data(root_tensor->place(), var_dtype); framework::TransDataType( *root_tensor, var->GetDataType(), &tmp_tensor); auto holder = tmp_tensor.MoveMemoryHolder(); root_tensor->ResetHolderWithType(holder, var_dtype); ++fp16_param; } if (place_ == root_tensor->place()) { ++persist_share; continue; } // reset tensor holder if (persist_param_vars_.find(name) != persist_param_vars_.end()) { // need offload param auto stream = static_cast(dev_ctx_)->stream(); if (IsNeedOffload(name)) { // add offload names offload_names_.insert(name); // offload moment Tensor2Pinned(root_tensor, stream); ++pinned_param; } else { // copy one device to other device auto src_place = root_tensor->place(); auto holder = root_tensor->MoveMemoryHolder(); auto dst_ptr = root_tensor->mutable_data( place_, root_tensor->dtype(), holder->size()); memory::Copy(place_, dst_ptr, src_place, holder->ptr(), holder->size(), stream); PADDLE_ENFORCE_EQ(phi::is_gpu_place(root_tensor->place()), true, common::errors::InvalidArgument( "The place of root tensor should be GPU.")); ++persist_reset; } } else { auto *ptr = thread_scope_->Var(name); PADDLE_ENFORCE_EQ(proto::VarType::DENSE_TENSOR, var->GetType(), common::errors::InvalidArgument( "The type of var should be DENSE_TENSOR.")); InitializeVariable(ptr, var->GetType()); DenseTensor *thread_tensor = ptr->GetMutable(); TensorCopy(*root_tensor, place_, thread_tensor); need_copy_vars_.push_back(name); // VLOG(0) << "need copy var name=" << name; } } } else { if (use_gpu_graph_ && use_ps_gpu_) { if (free_param_vars_.find(name) != free_param_vars_.end()) { del_var_names.push_back(name); // VLOG(0) << "unpersist need delete var name=" << name; } auto it = param_cast_vars_.find(name); if (it == param_cast_vars_.end()) { // sharding vars auto *ptr = thread_scope_->Var(name); InitializeVariable(ptr, var->GetType()); // set dims auto dims = phi::make_ddim(var->GetShape()); auto var_dtype = phi::TransToPhiDataType(static_cast(var->GetDataType())); ptr->GetMutable()->Resize(dims).set_type(var_dtype); } } } #endif } else { auto *ptr = thread_scope_->Var(name); InitializeVariable(ptr, var->GetType()); // amp auto it = cast_fp16_vars_.find(name); if (it != cast_fp16_vars_.end()) { auto desc_var = block.FindVar(it->second); if (desc_var != nullptr && desc_var->IsParameter()) { auto dims = phi::make_ddim(desc_var->GetShape()); auto var_dtype = phi::TransToPhiDataType(static_cast(var->GetDataType())); ptr->GetMutable()->Resize(dims).set_type(var_dtype); ++resize_var_cnt; } } } } // multi node delete unused vars if (!del_var_names.empty()) { root_scope_->EraseVars(del_var_names); } VLOG(0) << "device id=" << thread_id_ << ", total param count=" << all_param_.size() << ", persist count=" << persist_total << ", param=" << persist_param << ", fp16=" << fp16_param << ", share=" << persist_share << ", reset=" << persist_reset << ", pinned=" << pinned_param << ", resize_var=" << resize_var_cnt << ", need copy param count=" << need_copy_vars_.size() << ", delete vars count=" << del_var_names.size(); } void HogwildWorker::Finalize() { #ifdef PADDLE_WITH_HETERPS if (!sharding_mode_ && thread_id_ != 0) { return; } for (auto &name : need_copy_vars_) { Variable *root_var = root_scope_->FindVar(name); if (root_var == nullptr) { continue; } auto root_tensor = root_var->GetMutable(); Variable *var = thread_scope_->FindVar(name); auto tensor = var->Get(); TensorCopy(tensor, root_tensor->place(), root_tensor); } dev_ctx_->Wait(); #endif } template void HogwildWorker::SetZero(DenseTensor *tensor, const DenseTensor &root_tensor) { tensor->mutable_data(root_tensor.dims(), place_); phi::funcs::set_constant(*dev_ctx_, tensor, 0.0); } void HogwildWorker::BindingDataFeedMemory() { const std::vector &input_feed = device_reader_->GetUseSlotAlias(); for (auto const &name : input_feed) { device_reader_->AddFeedVar(thread_scope_->FindVar(name), name); } } void HogwildWorker::CreateDeviceResource(const ProgramDesc &main_prog) { BuildShardingDepends(main_prog); CreateThreadScope(main_prog); CreateThreadOperators(main_prog); #if defined(PADDLE_WITH_CUDA) && defined(PADDLE_WITH_HETERPS) && \ defined(PADDLE_WITH_PSCORE) if (use_gpu_graph_ && use_ps_gpu_) { float *stat_ptr = sync_stat_.mutable_data(place_, sizeof(float) * 3); float flags[] = {0.0, 1.0, 1.0}; auto stream = static_cast(dev_ctx_)->stream(); PADDLE_ENFORCE_GPU_SUCCESS(cudaMemcpyAsync(stat_ptr, // output &flags, sizeof(float) * 3, cudaMemcpyHostToDevice, stream)); PADDLE_ENFORCE_GPU_SUCCESS(cudaStreamSynchronize(stream)); } #endif } // check batch num bool HogwildWorker::CheckBatchNum(int flag) { float ret = 0.0; #if defined(PADDLE_WITH_CUDA) && defined(PADDLE_WITH_HETERPS) && \ defined(PADDLE_WITH_PSCORE) if (use_gpu_graph_ && use_ps_gpu_) { if (flag > 1) { flag = 1; } else if (flag < 0) { flag = 0; } // g_barrier.wait(); float *stat_ptr = sync_stat_.data(); int ring_id = 0; const auto &comm_context_manager = phi::distributed::CommContextManager::GetInstance(); phi::distributed::NCCLCommContext *comm_ctx = nullptr; PADDLE_ENFORCE_EQ(comm_context_manager.Has(std::to_string(ring_id)), true, common::errors::InvalidArgument( "You choose to use new communication library. " "But ring_id(%d) is " "not found in comm_context_manager.", std::to_string(ring_id))); comm_ctx = static_cast( comm_context_manager.Get(std::to_string(ring_id))); PADDLE_ENFORCE_NE(comm_ctx, nullptr, common::errors::Unavailable( "NCCLCommContext is nullptr, collective op should " "has ring_id attr.")); auto stream = static_cast(dev_ctx_)->stream(); // comm_ctx->AllReduce only support allreduce on the whole tensor, // single element is not supported now. PADDLE_ENFORCE_GPU_SUCCESS( phi::dynload::ncclAllReduce(&stat_ptr[flag], &stat_ptr[2], 1, ncclFloat32, ncclProd, comm_ctx->GetNcclComm(), stream)); PADDLE_ENFORCE_GPU_SUCCESS(cudaMemcpyAsync(&ret, // output &stat_ptr[2], sizeof(float), cudaMemcpyDeviceToHost, stream)); PADDLE_ENFORCE_GPU_SUCCESS(cudaStreamSynchronize(stream)); // g_barrier.wait(); } #endif return (ret > 0.0); } bool HogwildWorker::GetPassEnd(int flag) { float ret = 0.0; #if defined(PADDLE_WITH_CUDA) && defined(PADDLE_WITH_HETERPS) && \ defined(PADDLE_WITH_PSCORE) if (use_gpu_graph_ && use_ps_gpu_) { if (flag > 1) { flag = 1; } else if (flag < 0) { flag = 0; } // g_barrier.wait(); float *stat_ptr = sync_stat_.data(); auto comm = platform::NCCLCommContext::Instance().Get(ring_id_, place_.GetDeviceId()); // auto stream = static_cast(dev_ctx_)->stream(); // PADDLE_ENFORCE_GPU_SUCCESS(cudaStreamSynchronize(stream)); auto stream = comm->stream(); PADDLE_ENFORCE_GPU_SUCCESS(phi::dynload::ncclAllReduce(&stat_ptr[flag], &stat_ptr[2], 1, ncclFloat32, ncclProd, comm->comm(), stream)); PADDLE_ENFORCE_GPU_SUCCESS(cudaMemcpyAsync(&ret, // output &stat_ptr[2], sizeof(float), cudaMemcpyDeviceToHost, stream)); PADDLE_ENFORCE_GPU_SUCCESS(cudaStreamSynchronize(stream)); // g_barrier.wait(); } #endif return (ret > 0.0); } void HogwildWorker::TrainFilesWithProfiler() { platform::SetNumThreads(1); #if defined(PADDLE_WITH_HETERPS) && \ (defined(PADDLE_WITH_NCCL) || defined(PADDLE_WITH_RCCL)) platform::SetDeviceId(thread_id_); #elif defined(PADDLE_WITH_HETERPS) && defined(PADDLE_WITH_XPU_BKCL) platform::SetXPUDeviceId(thread_id_); #endif device_reader_->Start(); std::vector op_total_time; op_total_time.resize(ops_.size()); for (double &op_time : op_total_time) { op_time = 0.0; } platform::Timer timeline; double total_time = 0.0; double read_time = 0.0; int cur_batch = 0; int batch_cnt = 0; if (thread_id_ == 0) { quit_flag_.store(false); } g_barrier.wait(); timeline.Start(); uint64_t total_inst = 0; std::unique_ptr gc = nullptr; int64_t max_memory_size = GetEagerDeletionThreshold(); if (max_memory_size >= 0) { gc = CreateGarbageCollector(place_, max_memory_size); } bool infer_out_of_ins = false; while (true) { cur_batch = device_reader_->Next(); if (cur_batch <= 0 && !infer_out_of_ins) { break; } VLOG(3) << "read a batch in thread " << thread_id_; timeline.Pause(); read_time += timeline.ElapsedSec(); total_time += timeline.ElapsedSec(); if (infer_out_of_ins) { for (size_t i = 0; i < ops_.size(); ++i) { timeline.Start(); auto &op = ops_[i]; VLOG(3) << "Going to run op " << op_names_[i]; if (op->Type() == "c_broadcast") { op->Run(*thread_scope_, place_); } VLOG(3) << "Op " << op_names_[i] << " Finished"; timeline.Pause(); op_total_time[i] += timeline.ElapsedSec(); total_time += timeline.ElapsedSec(); if (gc) { DeleteUnusedTensors(*thread_scope_, op.get(), unused_vars_, gc.get()); } } } else { for (size_t i = 0; i < ops_.size(); ++i) { timeline.Start(); auto &op = ops_[i]; VLOG(3) << "Going to run op " << op_names_[i]; op->Run(*thread_scope_, place_); VLOG(3) << "Op " << op_names_[i] << " Finished"; timeline.Pause(); op_total_time[i] += timeline.ElapsedSec(); total_time += timeline.ElapsedSec(); if (gc) { DeleteUnusedTensors(*thread_scope_, op.get(), unused_vars_, gc.get()); } } } if (need_dump_field_) { DumpField(*thread_scope_, dump_mode_, dump_interval_); } if (need_dump_param_ && (sharding_mode_ || thread_id_ == 0)) { DumpParam(*thread_scope_, batch_cnt); } total_inst += cur_batch; ++batch_cnt; PrintFetchVars(); if (thread_id_ == 0) { if (batch_cnt > 0 && batch_cnt % 100 == 0) { for (size_t i = 0; i < ops_.size(); ++i) { fprintf(stderr, "op_name:[%zu][%s], op_mean_time:[%fs]\n", i, op_names_[i].c_str(), op_total_time[i] / batch_cnt); } fprintf(stderr, "mean read time: %fs\n", read_time / batch_cnt); fprintf(stderr, "IO percent: %f\n", read_time / total_time * 100); fprintf( stderr, "%6.2f instances/s\n", total_inst / total_time); // NOLINT } } if (gc) { gc->DirectClearCallback([this]() { thread_scope_->DropKids(); }); } else { thread_scope_->DropKids(); } timeline.Start(); } VLOG(0) << "GpuPs worker " << thread_id_ << " train cost " << total_time << " seconds, ins_num: " << total_inst << " read time: " << read_time << "seconds "; if (need_dump_field_ || need_dump_param_) { writer_.Flush(); } } void HogwildWorker::TrainFiles() { platform::SetNumThreads(1); platform::Timer timeline; timeline.Start(); int total_batch_num = 0; // how to accumulate fetched values here device_reader_->Start(); int cur_batch = 0; int batch_cnt = 0; if (thread_id_ == 0) { quit_flag_.store(false); // quit_flag_2 = false; } g_barrier.wait(); std::unique_ptr gc = nullptr; int64_t max_memory_size = GetEagerDeletionThreshold(); if (max_memory_size >= 0) { gc = CreateGarbageCollector(place_, max_memory_size); } bool infer_out_of_ins = false; while (true) { cur_batch = device_reader_->Next(); if (cur_batch <= 0 && !infer_out_of_ins) { break; } if (infer_out_of_ins) { for (auto &op : ops_) { if (op->Type() == "c_broadcast") { op->Run(*thread_scope_, place_); } if (gc) { DeleteUnusedTensors(*thread_scope_, op.get(), unused_vars_, gc.get()); } } } else { for (auto &op : ops_) { if (FLAGS_gpugraph_enable_print_op_debug) { VLOG(0) << "thread id=" << thread_id_ << ", " << op->DebugStringEx(thread_scope_); } op->Run(*thread_scope_, place_); if (gc) { DeleteUnusedTensors(*thread_scope_, op.get(), unused_vars_, gc.get()); } } } if (need_dump_field_) { DumpField(*thread_scope_, dump_mode_, dump_interval_); } if (need_dump_param_ && (sharding_mode_ || thread_id_ == 0)) { DumpParam(*thread_scope_, batch_cnt); } // for (auto var_name: thread_scope_->LocalVarNames()) { // // for (std::string& var_name : check_nan_var_names_) { // Variable* var = thread_scope_->FindVar(var_name); // if (var == nullptr) { // continue; // } // DenseTensor* tensor = var->GetMutable(); // if (tensor == nullptr || !tensor->IsInitialized()) { // continue; // } // if (framework::TensorContainsInf(*tensor) || // framework::TensorContainsNAN(*tensor)) { // static std::mutex mutex; // { // std::lock_guard lock(mutex); // VLOG(0) << "worker " << thread_id_ << ": " << var_name // << " contains inf or nan"; // // auto all_vars = thread_scope_->LocalVarNames(); // std::stringstream ss; // ss << "====== worker " << thread_id_ << "======\n"; // for (auto& local_var : thread_scope_->LocalVarNames()) { // platform::PrintVar(thread_scope_, local_var, local_var, &ss); // ss << "\n"; // } // std::cout << ss.str() << std::endl; // VLOG(0) << "worker " << thread_id_ << "print nan var done...."; // } // sleep(600); // exit(-1); // } // } total_batch_num += cur_batch; ++batch_cnt; PrintFetchVars(); if (gc) { gc->DirectClearCallback([this]() { thread_scope_->DropKids(); }); } else { thread_scope_->DropKids(); } } timeline.Pause(); VLOG(1) << "worker " << thread_id_ << " train cost " << timeline.ElapsedSec() << " seconds, batch_num: " << total_batch_num; if (need_dump_field_ || need_dump_param_) { writer_.Flush(); } } void HogwildWorker::PrintFetchVars() { // call count batch_num_++; int batch_per_print = fetch_config_.print_period(); int fetch_var_num = fetch_config_.fetch_var_names_size(); if (fetch_var_num == 0) { return; } if (thread_id_ == 0 && batch_num_ % batch_per_print == 0) { time_t curtime = 0; time(&curtime); std::array mbstr; std::strftime(mbstr.data(), sizeof(mbstr), "%Y-%m-%d %H:%M:%S", std::localtime(&curtime)); std::stringstream ss; ss << "time: [" << mbstr.data() << "], "; ss << "batch: [" << batch_num_ << "], "; for (int i = 0; i < fetch_var_num; ++i) { platform::PrintVar(thread_scope_, fetch_config_.fetch_var_names(i), fetch_config_.fetch_var_str_format(i), &ss); if (i < fetch_var_num - 1) { ss << ", "; } } std::cout << ss.str() << std::endl; } } } // namespace paddle::framework