!21793 Docking with cloud platform

Merge pull request !21793 from ZPaC/add-iteration-metrics
This commit is contained in:
i-robot 2021-08-14 10:56:44 +00:00 committed by Gitee
commit e885ea1b2f
21 changed files with 631 additions and 38 deletions

View File

@ -30,7 +30,7 @@
namespace mindspore {
namespace kernel {
// The duration between two downloading requests when return code is ResponseCode_SucNotReady.
// The duration between two PullWeights requests when return code is ResponseCode_SucNotReady.
constexpr int kRetryDurationOfPullWeights = 200;
template <typename T>
class FusedPullWeightKernel : public CPUKernel {
@ -51,9 +51,12 @@ class FusedPullWeightKernel : public CPUKernel {
MS_EXCEPTION_IF_NULL(fbb);
total_iteration_++;
uint64_t step_num_per_iteration = fl::worker::FLWorker::GetInstance().worker_step_num_per_iteration();
// The worker has to train kWorkerTrainStepNum standalone iterations before it communicates with server.
if (total_iteration_ % fl::worker::FLWorker::GetInstance().worker_step_num_per_iteration() !=
fl::kTrainBeginStepNum) {
MS_LOG(INFO) << "Try to pull weights. Local step number: " << total_iteration_
<< ", step number needs to run per iteration: " << step_num_per_iteration;
if (step_num_per_iteration != fl::kOneStepPerIteration &&
total_iteration_ % step_num_per_iteration != fl::kTrainBeginStepNum) {
return true;
}
@ -77,6 +80,7 @@ class FusedPullWeightKernel : public CPUKernel {
0, fbb->GetBufferPointer(), fbb->GetSize(), ps::core::TcpUserCommand::kPullWeight, &pull_weight_rsp_msg)) {
MS_LOG(WARNING) << "Sending request for FusedPullWeight to server 0 failed. Retry later.";
retcode = schema::ResponseCode_SucNotReady;
std::this_thread::sleep_for(std::chrono::milliseconds(kRetryDurationOfPullWeights));
continue;
}
MS_EXCEPTION_IF_NULL(pull_weight_rsp_msg);
@ -116,7 +120,7 @@ class FusedPullWeightKernel : public CPUKernel {
return false;
}
}
MS_LOG(INFO) << "Pull weights for " << weight_full_names_ << " succeed. Iteration: " << fl_iteration_;
MS_LOG(INFO) << "Pull weights for " << weight_full_names_ << " success. Iteration: " << fl_iteration_;
fl::worker::FLWorker::GetInstance().SetIterationRunning();
return true;
}

View File

@ -28,7 +28,7 @@
namespace mindspore {
namespace kernel {
// The duration between two uploading requests when return code is ResponseCode_SucNotReady.
// The duration between two PushWeights requests when return code is ResponseCode_SucNotReady.
constexpr int kRetryDurationOfPushWeights = 200;
template <typename T>
class FusedPushWeightKernel : public CPUKernel {
@ -49,9 +49,12 @@ class FusedPushWeightKernel : public CPUKernel {
MS_EXCEPTION_IF_NULL(fbb);
total_iteration_++;
uint64_t step_num_per_iteration = fl::worker::FLWorker::GetInstance().worker_step_num_per_iteration();
// The worker has to train kWorkerTrainStepNum standalone iterations before it communicates with server.
if (total_iteration_ % fl::worker::FLWorker::GetInstance().worker_step_num_per_iteration() !=
fl::kTrainBeginStepNum) {
MS_LOG(INFO) << "Try to push weights. Local step number: " << total_iteration_
<< ", step number needs to run per iteration: " << step_num_per_iteration;
if (step_num_per_iteration != fl::kOneStepPerIteration &&
total_iteration_ % step_num_per_iteration != fl::kTrainEndStepNum) {
return true;
}
@ -76,9 +79,9 @@ class FusedPushWeightKernel : public CPUKernel {
if (!fl::worker::FLWorker::GetInstance().SendToServer(i, fbb->GetBufferPointer(), fbb->GetSize(),
ps::core::TcpUserCommand::kPushWeight,
&push_weight_rsp_msg)) {
MS_LOG(WARNING) << "Sending request for FusedPushWeight to server " << i
<< " failed. This iteration is dropped.";
MS_LOG(WARNING) << "Sending request for FusedPushWeight to server " << i << " failed.";
retcode = schema::ResponseCode_SucNotReady;
std::this_thread::sleep_for(std::chrono::milliseconds(kRetryDurationOfPushWeights));
continue;
}
MS_EXCEPTION_IF_NULL(push_weight_rsp_msg);
@ -105,8 +108,7 @@ class FusedPushWeightKernel : public CPUKernel {
}
}
MS_LOG(INFO) << "Push weights for " << weight_full_names_ << " succeed. Iteration: " << fl_iteration_;
fl::worker::FLWorker::GetInstance().SetIterationCompleted();
MS_LOG(INFO) << "Push weights for " << weight_full_names_ << " success. Iteration: " << fl_iteration_;
return true;
}

View File

@ -187,6 +187,7 @@ constexpr size_t kCipherMgrMaxTaskNum = 64;
constexpr size_t kExecutorThreadPoolSize = 32;
constexpr size_t kExecutorMaxTaskNum = 32;
constexpr int kHttpSuccess = 200;
constexpr uint32_t kThreadSleepTime = 50;
constexpr auto kPBProtocol = "PB";
constexpr auto kFBSProtocol = "FBS";
constexpr auto kSuccess = "Success";

View File

@ -51,6 +51,18 @@ bool Executor::ReInitForScaling() {
return true;
}
bool Executor::ReInitForUpdatingHyperParams(size_t aggr_threshold) {
aggregation_count_ = aggr_threshold;
auto result = std::find_if(param_aggrs_.begin(), param_aggrs_.end(), [this](auto param_aggr) {
return !param_aggr.second->ReInitForUpdatingHyperParams(aggregation_count_);
});
if (result != param_aggrs_.end()) {
MS_LOG(ERROR) << "Reinitializing aggregator of " << result->first << " for scaling failed.";
return false;
}
return true;
}
bool Executor::initialized() const { return initialized_; }
bool Executor::HandlePush(const std::string &param_name, const UploadData &upload_data) {

View File

@ -33,8 +33,6 @@
namespace mindspore {
namespace fl {
namespace server {
constexpr int kThreadSleepTime = 5;
// Executor is the entrance for server to handle aggregation, optimizing, model querying, etc. It handles
// logics relevant to kernel launching.
class Executor {
@ -53,6 +51,9 @@ class Executor {
// Reinitialize parameter aggregators after scaling operations are done.
bool ReInitForScaling();
// After hyper-parameters are updated, some parameter aggregators should be reinitialized.
bool ReInitForUpdatingHyperParams(size_t aggr_threshold);
// Called in parameter server training mode to do Push operation.
// For the same trainable parameter, HandlePush method must be called aggregation_count_ times before it's considered
// as completed.

View File

@ -26,6 +26,14 @@ namespace mindspore {
namespace fl {
namespace server {
class Server;
Iteration::~Iteration() {
move_to_next_thread_running_ = false;
if (move_to_next_thread_.joinable()) {
move_to_next_thread_.join();
}
}
void Iteration::RegisterMessageCallback(const std::shared_ptr<ps::core::TcpCommunicator> &communicator) {
MS_EXCEPTION_IF_NULL(communicator);
communicator_ = communicator;
@ -79,9 +87,27 @@ void Iteration::InitRounds(const std::vector<std::shared_ptr<ps::core::Communica
});
LocalMetaStore::GetInstance().put_value(kCtxTotalTimeoutDuration, iteration_time_window);
MS_LOG(INFO) << "Time window for one iteration is " << iteration_time_window;
// Initialize the thread which will handle the signal from round kernels.
move_to_next_thread_ = std::thread([this]() {
while (move_to_next_thread_running_.load()) {
std::unique_lock<std::mutex> lock(next_iteration_mutex_);
next_iteration_cv_.wait(lock);
MoveToNextIteration(is_last_iteration_valid_, move_to_next_reason_);
}
});
return;
}
void Iteration::ClearRounds() { rounds_.clear(); }
void Iteration::NotifyNext(bool is_last_iter_valid, const std::string &reason) {
std::unique_lock<std::mutex> lock(next_iteration_mutex_);
is_last_iteration_valid_ = is_last_iter_valid;
move_to_next_reason_ = reason;
next_iteration_cv_.notify_one();
}
void Iteration::MoveToNextIteration(bool is_last_iter_valid, const std::string &reason) {
MS_LOG(INFO) << "Notify cluster starts to proceed to next iteration. Iteration is " << iteration_num_
<< " validation is " << is_last_iter_valid << ". Reason: " << reason;
@ -119,7 +145,10 @@ void Iteration::SetIterationRunning() {
// This event helps worker/server to be consistent in iteration state.
server_node_->BroadcastEvent(static_cast<uint32_t>(ps::CustomEvent::kIterationRunning));
}
std::unique_lock<std::mutex> lock(iteration_state_mtx_);
iteration_state_ = IterationState::kRunning;
start_timestamp_ = LongToUlong(CURRENT_TIME_MILLI.count());
}
void Iteration::SetIterationCompleted() {
@ -129,13 +158,17 @@ void Iteration::SetIterationCompleted() {
// This event helps worker/server to be consistent in iteration state.
server_node_->BroadcastEvent(static_cast<uint32_t>(ps::CustomEvent::kIterationCompleted));
}
std::unique_lock<std::mutex> lock(iteration_state_mtx_);
iteration_state_ = IterationState::kCompleted;
complete_timestamp_ = LongToUlong(CURRENT_TIME_MILLI.count());
}
void Iteration::ScalingBarrier() {
MS_LOG(INFO) << "Starting Iteration scaling barrier.";
while (iteration_state_.load() != IterationState::kCompleted) {
std::this_thread::yield();
std::unique_lock<std::mutex> lock(iteration_state_mtx_);
if (iteration_state_.load() != IterationState::kCompleted) {
iteration_state_cv_.wait(lock);
}
MS_LOG(INFO) << "Ending Iteration scaling barrier.";
}
@ -156,14 +189,144 @@ bool Iteration::ReInitForScaling(uint32_t server_num, uint32_t server_rank) {
return true;
}
bool Iteration::ReInitForUpdatingHyperParams(const std::vector<RoundConfig> &updated_rounds_config) {
for (const auto &updated_round : updated_rounds_config) {
for (const auto &round : rounds_) {
if (updated_round.name == round->name()) {
MS_LOG(INFO) << "Reinitialize for round " << round->name();
if (!round->ReInitForUpdatingHyperParams(updated_round.threshold_count, updated_round.time_window)) {
MS_LOG(ERROR) << "Reinitializing for round " << round->name() << " failed.";
return false;
}
}
}
}
return true;
}
const std::vector<std::shared_ptr<Round>> &Iteration::rounds() const { return rounds_; }
bool Iteration::is_last_iteration_valid() const { return is_last_iteration_valid_; }
void Iteration::set_metrics(const std::shared_ptr<IterationMetrics> &metrics) { metrics_ = metrics; }
void Iteration::set_loss(float loss) { loss_ = loss; }
void Iteration::set_accuracy(float accuracy) { accuracy_ = accuracy; }
InstanceState Iteration::instance_state() const { return instance_state_.load(); }
bool Iteration::EnableServerInstance(std::string *result) {
MS_ERROR_IF_NULL_W_RET_VAL(result, false);
// Before enabling server instance, we should judge whether this request should be handled.
std::unique_lock<std::mutex> lock(instance_mtx_);
if (is_instance_being_updated_) {
*result = "The instance is being updated. Please retry enabling server later.";
MS_LOG(WARNING) << result;
return false;
}
if (instance_state_.load() == InstanceState::kFinish) {
*result = "The instance is completed. Please do not enabling server now.";
MS_LOG(WARNING) << result;
return false;
}
// Start enabling server instance.
is_instance_being_updated_ = true;
instance_state_ = InstanceState::kRunning;
*result = "Enabling FL-Server succeeded.";
// End enabling server instance.
is_instance_being_updated_ = false;
return true;
}
bool Iteration::DisableServerInstance(std::string *result) {
MS_ERROR_IF_NULL_W_RET_VAL(result, false);
// Before disabling server instance, we should judge whether this request should be handled.
std::unique_lock<std::mutex> lock(instance_mtx_);
if (is_instance_being_updated_) {
*result = "The instance is being updated. Please retry disabling server later.";
MS_LOG(WARNING) << *result;
return false;
}
if (instance_state_.load() == InstanceState::kFinish) {
*result = "The instance is completed. Please do not disabling server now.";
MS_LOG(WARNING) << *result;
return false;
}
if (instance_state_.load() == InstanceState::kDisable) {
*result = "Disabling FL-Server succeeded.";
MS_LOG(INFO) << *result;
return false;
}
// Start disabling server instance.
is_instance_being_updated_ = true;
// If instance is running, we should drop current iteration and move to the next.
instance_state_ = InstanceState::kDisable;
if (!ForciblyMoveToNextIteration()) {
*result = "Disabling instance failed. Can't drop current iteration and move to the next.";
MS_LOG(ERROR) << result;
return false;
}
// End disabling server instance.
is_instance_being_updated_ = false;
return true;
}
bool Iteration::NewInstance(const nlohmann::json &new_instance_json, std::string *result) {
MS_ERROR_IF_NULL_W_RET_VAL(result, false);
// Before new instance, we should judge whether this request should be handled.
std::unique_lock<std::mutex> lock(instance_mtx_);
if (is_instance_being_updated_) {
*result = "The instance is being updated. Please retry new instance later.";
MS_LOG(WARNING) << *result;
return false;
}
// Start new server instance.
is_instance_being_updated_ = true;
// Reset current instance.
instance_state_ = InstanceState::kFinish;
MS_LOG(INFO) << "Proceed to a new instance.";
WaitAllRoundsFinish();
for (auto &round : rounds_) {
MS_ERROR_IF_NULL_W_RET_VAL(round, false);
round->Reset();
}
iteration_num_ = 1;
LocalMetaStore::GetInstance().set_curr_iter_num(iteration_num_);
ModelStore::GetInstance().Reset();
// Update the hyper-parameters on server and reinitialize rounds.
if (!UpdateHyperParams(new_instance_json)) {
*result = "Updating hyper-parameters failed.";
return false;
}
if (!ReInitRounds()) {
*result = "Reinitializing rounds failed.";
return false;
}
instance_state_ = InstanceState::kRunning;
*result = "New FL-Server instance succeeded.";
// End new server instance.
is_instance_being_updated_ = false;
return true;
}
void Iteration::WaitAllRoundsFinish() {
while (running_round_num_.load() != 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(kThreadSleepTime));
}
}
bool Iteration::SyncIteration(uint32_t rank) {
MS_ERROR_IF_NULL_W_RET_VAL(communicator_, false);
SyncIterationRequest sync_iter_req;
@ -320,6 +483,7 @@ void Iteration::HandlePrepareForNextIterRequest(const std::shared_ptr<ps::core::
void Iteration::PrepareForNextIter() {
MS_LOG(INFO) << "Prepare for next iteration. Switch the server to safemode.";
Server::GetInstance().SwitchToSafeMode();
WaitAllRoundsFinish();
}
bool Iteration::BroadcastMoveToNextIterRequest(bool is_last_iter_valid, const std::string &reason) {
@ -436,24 +600,134 @@ void Iteration::HandleEndLastIterRequest(const std::shared_ptr<ps::core::Message
void Iteration::EndLastIter() {
MS_LOG(INFO) << "End the last iteration " << iteration_num_;
iteration_num_++;
// After the job is done, reset the iteration to the initial number and reset ModelStore.
if (iteration_num_ > ps::PSContext::instance()->fl_iteration_num()) {
if (iteration_num_ == ps::PSContext::instance()->fl_iteration_num()) {
MS_LOG(INFO) << "Iteration loop " << iteration_loop_count_
<< " is completed. Iteration number: " << ps::PSContext::instance()->fl_iteration_num();
iteration_num_ = 1;
iteration_loop_count_++;
ModelStore::GetInstance().Reset();
instance_state_ = InstanceState::kFinish;
}
std::unique_lock<std::mutex> lock(pinned_mtx_);
pinned_iter_num_ = 0;
lock.unlock();
SetIterationCompleted();
SummarizeIteration();
iteration_num_++;
LocalMetaStore::GetInstance().set_curr_iter_num(iteration_num_);
Server::GetInstance().CancelSafeMode();
SetIterationCompleted();
iteration_state_cv_.notify_all();
MS_LOG(INFO) << "Move to next iteration:" << iteration_num_ << "\n";
}
bool Iteration::ForciblyMoveToNextIteration() {
NotifyNext(false, "Forcibly move to next iteration.");
return true;
}
bool Iteration::SummarizeIteration() {
// If the metrics_ is not initialized or the server is not the leader server, do not summarize.
if (server_node_->rank_id() != kLeaderServerRank || metrics_ == nullptr) {
MS_LOG(INFO) << "This server will not summarize for iteration.";
return true;
}
metrics_->set_fl_name(ps::PSContext::instance()->fl_name());
metrics_->set_fl_iteration_num(ps::PSContext::instance()->fl_iteration_num());
metrics_->set_cur_iteration_num(iteration_num_ - 1);
metrics_->set_instance_state(instance_state_.load());
metrics_->set_loss(loss_);
metrics_->set_accuracy(accuracy_);
// The joined client number is equal to the threshold of updateModel.
size_t update_model_threshold = static_cast<size_t>(
std::ceil(ps::PSContext::instance()->start_fl_job_threshold() * ps::PSContext::instance()->update_model_ratio()));
metrics_->set_joined_client_num(update_model_threshold);
// The rejected client number is equal to threshold of startFLJob minus threshold of updateModel.
metrics_->set_rejected_client_num(ps::PSContext::instance()->start_fl_job_threshold() - update_model_threshold);
if (complete_timestamp_ < start_timestamp_) {
MS_LOG(ERROR) << "The complete_timestamp_: " << complete_timestamp_ << ", start_timestamp_: " << start_timestamp_
<< ". One of them is invalid.";
metrics_->set_iteration_time_cost(UINT64_MAX);
} else {
metrics_->set_iteration_time_cost(complete_timestamp_ - start_timestamp_);
}
metrics_->Summarize();
return true;
}
bool Iteration::UpdateHyperParams(const nlohmann::json &json) {
for (const auto &item : json.items()) {
std::string key = item.key();
if (key == "start_fl_job_threshold") {
ps::PSContext::instance()->set_start_fl_job_threshold(item.value().get<uint64_t>());
continue;
}
if (key == "start_fl_job_time_window") {
ps::PSContext::instance()->set_start_fl_job_time_window(item.value().get<uint64_t>());
continue;
}
if (key == "update_model_ratio") {
ps::PSContext::instance()->set_update_model_ratio(item.value().get<float>());
continue;
}
if (key == "update_model_time_window") {
ps::PSContext::instance()->set_update_model_time_window(item.value().get<uint64_t>());
continue;
}
if (key == "fl_iteration_num") {
ps::PSContext::instance()->set_fl_iteration_num(item.value().get<uint64_t>());
continue;
}
if (key == "client_epoch_num") {
ps::PSContext::instance()->set_client_epoch_num(item.value().get<uint64_t>());
continue;
}
if (key == "client_batch_size") {
ps::PSContext::instance()->set_client_batch_size(item.value().get<uint64_t>());
continue;
}
if (key == "client_learning_rate") {
ps::PSContext::instance()->set_client_learning_rate(item.value().get<float>());
continue;
}
}
return true;
}
bool Iteration::ReInitRounds() {
size_t start_fl_job_threshold = ps::PSContext::instance()->start_fl_job_threshold();
float update_model_ratio = ps::PSContext::instance()->update_model_ratio();
size_t update_model_threshold = static_cast<size_t>(std::ceil(start_fl_job_threshold * update_model_ratio));
uint64_t start_fl_job_time_window = ps::PSContext::instance()->start_fl_job_time_window();
uint64_t update_model_time_window = ps::PSContext::instance()->update_model_time_window();
std::vector<RoundConfig> new_round_config = {
{"startFLJob", true, start_fl_job_time_window, true, start_fl_job_threshold},
{"updateModel", true, update_model_time_window, true, update_model_threshold}};
if (!Iteration::GetInstance().ReInitForUpdatingHyperParams(new_round_config)) {
MS_LOG(ERROR) << "Reinitializing for updating hyper-parameters failed.";
return false;
}
size_t executor_threshold = 0;
const std::string &server_mode = ps::PSContext::instance()->server_mode();
uint32_t worker_num = ps::PSContext::instance()->initial_worker_num();
if (server_mode == ps::kServerModeFL || server_mode == ps::kServerModeHybrid) {
executor_threshold = update_model_threshold;
} else if (server_mode == ps::kServerModePS) {
executor_threshold = worker_num;
} else {
MS_LOG(ERROR) << "Server mode " << server_mode << " is not supported.";
return false;
}
if (!Executor::GetInstance().ReInitForUpdatingHyperParams(executor_threshold)) {
MS_LOG(ERROR) << "Reinitializing executor failed.";
return false;
}
return true;
}
} // namespace server
} // namespace fl
} // namespace mindspore

View File

@ -24,6 +24,7 @@
#include "fl/server/common.h"
#include "fl/server/round.h"
#include "fl/server/local_meta_store.h"
#include "fl/server/iteration_metrics.h"
namespace mindspore {
namespace fl {
@ -38,6 +39,7 @@ enum class IterationState {
// The time duration between retrying when sending prepare for next iteration request failed.
constexpr uint32_t kRetryDurationForPrepareForNextIter = 500;
class IterationMetrics;
// In server's logic, Iteration is the minimum execution unit. For each execution, it consists of multiple kinds of
// Rounds, only after all the rounds are finished, this iteration is considered as completed.
class Iteration {
@ -60,6 +62,12 @@ class Iteration {
void InitRounds(const std::vector<std::shared_ptr<ps::core::CommunicatorBase>> &communicators,
const TimeOutCb &timeout_cb, const FinishIterCb &finish_iteration_cb);
// Release all the round objects in Iteration instance. Used for reinitializing round and round kernels.
void ClearRounds();
// Notify move_to_next_thread_ to move to next iteration.
void NotifyNext(bool is_last_iter_valid, const std::string &reason);
// This method will control servers to proceed to next iteration.
// There's communication between leader and follower servers in this method.
// The server moves to next iteration only after the last round finishes or the time expires.
@ -79,28 +87,65 @@ class Iteration {
// The server number after scaling is required in some rounds.
bool ReInitForScaling(uint32_t server_num, uint32_t server_rank);
// After hyper-parameters are updated, some rounds and kernels should be reinitialized.
bool ReInitForUpdatingHyperParams(const std::vector<RoundConfig> &updated_rounds_config);
const std::vector<std::shared_ptr<Round>> &rounds() const;
bool is_last_iteration_valid() const;
// Set the instance metrics which will be called for each iteration.
void set_metrics(const std::shared_ptr<IterationMetrics> &metrics);
void set_loss(float loss);
void set_accuracy(float accuracy);
// Return state of current training job instance.
InstanceState instance_state() const;
// Return whether current instance is being updated.
bool IsInstanceBeingUpdated() const;
// EnableFLS/disableFLS the current training instance.
bool EnableServerInstance(std::string *result);
bool DisableServerInstance(std::string *result);
// Finish current instance and start a new one. FLPlan could be changed in this method.
bool NewInstance(const nlohmann::json &new_instance_json, std::string *result);
// Query information of current instance.
bool QueryInstance(std::string *result);
// Need to wait all the rounds to finish before proceed to next iteration.
void WaitAllRoundsFinish();
// The round kernels whose Launch method has not returned yet.
std::atomic_uint32_t running_round_num_;
private:
Iteration()
: server_node_(nullptr),
: running_round_num_(0),
server_node_(nullptr),
communicator_(nullptr),
iteration_state_(IterationState::kCompleted),
start_timestamp_(0),
complete_timestamp_(0),
iteration_loop_count_(0),
iteration_num_(1),
is_last_iteration_valid_(true),
move_to_next_reason_(""),
move_to_next_thread_running_(true),
pinned_iter_num_(0),
metrics_(nullptr),
instance_state_(InstanceState::kRunning),
is_instance_being_updated_(false),
loss_(0.0),
accuracy_(0.0) {
accuracy_(0.0),
joined_client_num_(0),
rejected_client_num_(0),
time_cost_(0) {
LocalMetaStore::GetInstance().set_curr_iter_num(iteration_num_);
}
~Iteration() = default;
~Iteration();
Iteration(const Iteration &) = delete;
Iteration &operator=(const Iteration &) = delete;
@ -138,6 +183,18 @@ class Iteration {
// The server end the last iteration. This method will increase the iteration number and cancel the safemode.
void EndLastIter();
// Drop current iteration and move to the next immediately.
bool ForciblyMoveToNextIteration();
// Summarize metrics for the completed iteration, including iteration time cost, accuracy, loss, etc.
bool SummarizeIteration();
// Update server's hyper-parameters according to the given serialized json(hyper_params_data).
bool UpdateHyperParams(const nlohmann::json &json);
// Reinitialize rounds and round kernels.
bool ReInitRounds();
std::shared_ptr<ps::core::ServerNode> server_node_;
std::shared_ptr<ps::core::TcpCommunicator> communicator_;
@ -145,7 +202,11 @@ class Iteration {
std::vector<std::shared_ptr<Round>> rounds_;
// The iteration is either running or completed at any time.
std::mutex iteration_state_mtx_;
std::condition_variable iteration_state_cv_;
std::atomic<IterationState> iteration_state_;
uint64_t start_timestamp_;
uint64_t complete_timestamp_;
// The count of iteration loops which are completed.
size_t iteration_loop_count_;
@ -153,18 +214,44 @@ class Iteration {
// Server's current iteration number.
size_t iteration_num_;
// Last iteration is successfully finished.
// Whether last iteration is successfully finished and the reason.
bool is_last_iteration_valid_;
std::string move_to_next_reason_;
// It will be notified by rounds that the instance moves to the next iteration.
std::thread move_to_next_thread_;
std::atomic_bool move_to_next_thread_running_;
std::mutex next_iteration_mutex_;
std::condition_variable next_iteration_cv_;
// To avoid Next method is called multiple times in one iteration, we should mark the iteration number.
uint64_t pinned_iter_num_;
std::mutex pinned_mtx_;
std::shared_ptr<IterationMetrics> metrics_;
// The state for current instance.
std::atomic<InstanceState> instance_state_;
// Every instance is not reentrant.
// This flag represents whether the instance is being updated.
std::mutex instance_mtx_;
bool is_instance_being_updated_;
// The training loss after this federated learning iteration, passed by worker.
float loss_;
// The evaluation result after this federated learning iteration, passed by worker.
float accuracy_;
// The number of clients which join the federated aggregation.
size_t joined_client_num_;
// The number of clients which are not involved in federated aggregation.
size_t rejected_client_num_;
// The time cost in millisecond for this completed iteration.
uint64_t time_cost_;
};
} // namespace server
} // namespace fl

View File

@ -14,11 +14,11 @@
* limitations under the License.
*/
#include "fl/server/iteration_metrics.h"
#include <string>
#include <fstream>
#include "debug/common.h"
#include "ps/constants.h"
#include "fl/server/iteration_metrics.h"
namespace mindspore {
namespace fl {

View File

@ -67,6 +67,8 @@ class AggregationKernel : public CPUKernel {
// Reinitialize aggregation kernel after scaling operations are done.
virtual bool ReInitForScaling() { return true; }
virtual bool ReInitForUpdatingHyperParams(size_t) { return true; }
// Setter and getter of kernels parameters information.
void set_params_info(const ParamsInfo &params_info) { params_info_ = params_info; }
const std::vector<std::string> &input_names() { return params_info_.inputs_names(); }

View File

@ -178,6 +178,12 @@ class FedAvgKernel : public AggregationKernel {
return true;
}
bool ReInitForUpdatingHyperParams(size_t aggr_threshold) override {
done_count_ = aggr_threshold;
DistributedCountService::GetInstance().RegisterCounter(name_, done_count_, {first_cnt_handler_, last_cnt_handler_});
return true;
}
private:
void GenerateReuseKernelNodeInfo() override {
MS_LOG(INFO) << "FedAvg reuse 'weight' of the kernel node.";

View File

@ -102,7 +102,6 @@ void ModelStore::Reset() {
initial_model_ = iteration_to_model_.rbegin()->second;
iteration_to_model_.clear();
iteration_to_model_[kInitIterationNum] = initial_model_;
iteration_to_model_[kResetInitIterNum] = initial_model_;
}
const std::map<size_t, std::shared_ptr<MemoryRegister>> &ModelStore::iteration_to_model() {

View File

@ -60,6 +60,21 @@ bool ParameterAggregator::ReInitForScaling() {
return true;
}
bool ParameterAggregator::ReInitForUpdatingHyperParams(size_t aggr_threshold) {
required_push_count_ = aggr_threshold;
required_pull_count_ = aggr_threshold;
auto result = std::find_if(aggregation_kernel_parameters_.begin(), aggregation_kernel_parameters_.end(),
[aggr_threshold](auto aggregation_kernel) {
MS_ERROR_IF_NULL_W_RET_VAL(aggregation_kernel.first, true);
return !aggregation_kernel.first->ReInitForUpdatingHyperParams(aggr_threshold);
});
if (result != aggregation_kernel_parameters_.end()) {
MS_LOG(ERROR) << "Reinitializing aggregation kernel after scaling failed";
return false;
}
return true;
}
bool ParameterAggregator::UpdateData(const std::map<std::string, Address> &new_data) {
std::map<std::string, AddressPtr> &name_to_addr = memory_register_->addresses();
for (const auto &data : new_data) {

View File

@ -68,6 +68,9 @@ class ParameterAggregator {
// Reinitialize the parameter aggregator after scaling operations are done.
bool ReInitForScaling();
// After hyper-parameters are updated, some parameter aggregators should be reinitialized.
bool ReInitForUpdatingHyperParams(size_t aggr_threshold);
// Update old data stored in ParameterAggregator with new data.
// The data could have many meanings: weights, gradients, learning_rate, momentum, etc.
bool UpdateData(const std::map<std::string, Address> &new_data);

View File

@ -102,6 +102,21 @@ bool Round::ReInitForScaling(uint32_t server_num) {
return true;
}
bool Round::ReInitForUpdatingHyperParams(size_t updated_threshold_count, size_t updated_time_window) {
time_window_ = updated_time_window;
threshold_count_ = updated_threshold_count;
if (check_count_) {
auto first_count_handler = std::bind(&Round::OnFirstCountEvent, this, std::placeholders::_1);
auto last_count_handler = std::bind(&Round::OnLastCountEvent, this, std::placeholders::_1);
DistributedCountService::GetInstance().RegisterCounter(name_, threshold_count_,
{first_count_handler, last_count_handler});
}
MS_ERROR_IF_NULL_W_RET_VAL(kernel_, false);
kernel_->InitKernel(threshold_count_);
return true;
}
void Round::BindRoundKernel(const std::shared_ptr<kernel::RoundKernel> &kernel) {
MS_EXCEPTION_IF_NULL(kernel);
kernel_ = kernel;
@ -114,10 +129,9 @@ void Round::LaunchRoundKernel(const std::shared_ptr<ps::core::MessageHandler> &m
MS_ERROR_IF_NULL_WO_RET_VAL(message);
MS_ERROR_IF_NULL_WO_RET_VAL(kernel_);
MS_ERROR_IF_NULL_WO_RET_VAL(communicator_);
// If the server is still in the process of scaling, refuse the request.
if (Server::GetInstance().IsSafeMode()) {
MS_LOG(WARNING) << "The cluster is still in process of scaling, please retry " << name_ << " later.";
std::string reason = "The cluster is in safemode.";
std::string reason = "";
if (!IsServerAvailable(&reason)) {
if (!communicator_->SendResponse(reason.c_str(), reason.size(), message)) {
MS_LOG(ERROR) << "Sending response failed.";
return;
@ -125,6 +139,7 @@ void Round::LaunchRoundKernel(const std::shared_ptr<ps::core::MessageHandler> &m
return;
}
Iteration::GetInstance().running_round_num_++;
AddressPtr input = std::make_shared<Address>();
AddressPtr output = std::make_shared<Address>();
MS_ERROR_IF_NULL_WO_RET_VAL(input);
@ -133,7 +148,7 @@ void Round::LaunchRoundKernel(const std::shared_ptr<ps::core::MessageHandler> &m
input->size = message->len();
bool ret = kernel_->Launch({input}, {}, {output});
if (output->size == 0) {
std::string reason = "The output of the round " + name_ + " is empty.";
reason = "The output of the round " + name_ + " is empty.";
MS_LOG(WARNING) << reason;
if (!communicator_->SendResponse(reason.c_str(), reason.size(), message)) {
MS_LOG(ERROR) << "Sending response failed.";
@ -149,9 +164,10 @@ void Round::LaunchRoundKernel(const std::shared_ptr<ps::core::MessageHandler> &m
// Must send response back no matter what value Launch method returns.
if (!ret) {
std::string reason = "Launching round kernel of round " + name_ + " failed.";
Iteration::GetInstance().MoveToNextIteration(false, reason);
reason = "Launching round kernel of round " + name_ + " failed.";
Iteration::GetInstance().NotifyNext(false, reason);
}
Iteration::GetInstance().running_round_num_--;
return;
}
@ -195,6 +211,30 @@ void Round::OnLastCountEvent(const std::shared_ptr<ps::core::MessageHandler> &me
kernel_->OnLastCountEvent(message);
return;
}
bool Round::IsServerAvailable(std::string *reason) {
MS_ERROR_IF_NULL_W_RET_VAL(reason, false);
// After one instance is completed, the model should be accessed by clients.
if (Iteration::GetInstance().instance_state() == InstanceState::kFinish && name_ == "getModel") {
return true;
}
// If the server state is Disable or Finish, refuse the request.
if (Iteration::GetInstance().instance_state() == InstanceState::kDisable ||
Iteration::GetInstance().instance_state() == InstanceState::kFinish) {
MS_LOG(WARNING) << "The server's training job is disabled or finished, please retry " + name_ + " later.";
*reason = ps::kJobNotAvailable;
return false;
}
// If the server is still in the process of scaling, reject the request.
if (Server::GetInstance().IsSafeMode()) {
MS_LOG(WARNING) << "The cluster is still in process of scaling, please retry " << name_ << " later.";
*reason = ps::kClusterSafeMode;
return false;
}
return true;
}
} // namespace server
} // namespace fl
} // namespace mindspore

View File

@ -43,6 +43,9 @@ class Round {
// Reinitialize count service and round kernel of this round after scaling operations are done.
bool ReInitForScaling(uint32_t server_num);
// After hyper-parameters are updated, some rounds and kernels should be reinitialized.
bool ReInitForUpdatingHyperParams(size_t updated_threshold_count, size_t updated_time_window);
// Bind a round kernel to this Round. This method should be called after Initialize.
void BindRoundKernel(const std::shared_ptr<kernel::RoundKernel> &kernel);
@ -63,6 +66,9 @@ class Round {
void OnFirstCountEvent(const std::shared_ptr<ps::core::MessageHandler> &message);
void OnLastCountEvent(const std::shared_ptr<ps::core::MessageHandler> &message);
// Judge whether the training service is available.
bool IsServerAvailable(std::string *reason);
std::string name_;
// Whether this round needs to use timer. Most rounds in federated learning with mobile devices scenario need to set

View File

@ -80,6 +80,7 @@ void Server::Run() {
MS_LOG(INFO) << "Parameters for secure aggregation have been initiated.";
}
RegisterRoundKernel();
InitMetrics();
MS_LOG(INFO) << "Server started successfully.";
safemode_ = false;
lock.unlock();
@ -306,6 +307,8 @@ void Server::RegisterCommCallbacks() {
// Set exception event callbacks for server.
RegisterExceptionEventCallback(tcp_comm);
// Set message callbacks for server.
RegisterMessageCallback(tcp_comm);
if (!server_node_->InitFollowerScaler()) {
MS_LOG(EXCEPTION) << "Initializing follower elastic scaler failed.";
@ -354,6 +357,19 @@ void Server::RegisterExceptionEventCallback(const std::shared_ptr<ps::core::TcpC
});
}
void Server::RegisterMessageCallback(const std::shared_ptr<ps::core::TcpCommunicator> &communicator) {
MS_EXCEPTION_IF_NULL(communicator);
// Register handler for restful requests receviced by scheduler.
communicator->RegisterMsgCallBack("enableFLS",
std::bind(&Server::HandleEnableServerRequest, this, std::placeholders::_1));
communicator->RegisterMsgCallBack("disableFLS",
std::bind(&Server::HandleDisableServerRequest, this, std::placeholders::_1));
communicator->RegisterMsgCallBack("newInstance",
std::bind(&Server::HandleNewInstanceRequest, this, std::placeholders::_1));
communicator->RegisterMsgCallBack("queryInstance",
std::bind(&Server::HandleQueryInstanceRequest, this, std::placeholders::_1));
}
void Server::InitExecutor() {
MS_EXCEPTION_IF_NULL(func_graph_);
if (executor_threshold_ == 0) {
@ -392,6 +408,19 @@ void Server::RegisterRoundKernel() {
return;
}
void Server::InitMetrics() {
if (server_node_->rank_id() == kLeaderServerRank) {
MS_EXCEPTION_IF_NULL(iteration_);
std::shared_ptr<IterationMetrics> iteration_metrics =
std::make_shared<IterationMetrics>(ps::PSContext::instance()->config_file_path());
if (!iteration_metrics->Initialize()) {
MS_LOG(WARNING) << "Initializing metrics failed.";
return;
}
iteration_->set_metrics(iteration_metrics);
}
}
void Server::StartCommunicator() {
if (communicators_with_worker_.empty()) {
MS_LOG(EXCEPTION) << "Communicators for communication with worker is empty.";
@ -489,6 +518,92 @@ void Server::ProcessAfterScalingIn() {
std::this_thread::sleep_for(std::chrono::milliseconds(kServerSleepTimeForNetworking));
safemode_ = false;
}
void Server::HandleEnableServerRequest(const std::shared_ptr<ps::core::MessageHandler> &message) {
MS_ERROR_IF_NULL_WO_RET_VAL(message);
MS_ERROR_IF_NULL_WO_RET_VAL(iteration_);
MS_ERROR_IF_NULL_WO_RET_VAL(communicator_with_server_);
auto tcp_comm = std::dynamic_pointer_cast<ps::core::TcpCommunicator>(communicator_with_server_);
MS_ERROR_IF_NULL_WO_RET_VAL(tcp_comm);
std::string result_message = "";
bool result = iteration_->EnableServerInstance(&result_message);
nlohmann::json response;
response["result"] = result;
response["message"] = result_message;
if (!tcp_comm->SendResponse(response.dump().c_str(), response.dump().size(), message)) {
MS_LOG(ERROR) << "Sending response failed.";
return;
}
}
void Server::HandleDisableServerRequest(const std::shared_ptr<ps::core::MessageHandler> &message) {
MS_ERROR_IF_NULL_WO_RET_VAL(message);
MS_ERROR_IF_NULL_WO_RET_VAL(iteration_);
MS_ERROR_IF_NULL_WO_RET_VAL(communicator_with_server_);
auto tcp_comm = std::dynamic_pointer_cast<ps::core::TcpCommunicator>(communicator_with_server_);
MS_ERROR_IF_NULL_WO_RET_VAL(tcp_comm);
std::string result_message = "";
bool result = iteration_->DisableServerInstance(&result_message);
nlohmann::json response;
response["result"] = result;
response["message"] = result_message;
if (!tcp_comm->SendResponse(response.dump().c_str(), response.dump().size(), message)) {
MS_LOG(ERROR) << "Sending response failed.";
return;
}
}
void Server::HandleNewInstanceRequest(const std::shared_ptr<ps::core::MessageHandler> &message) {
MS_ERROR_IF_NULL_WO_RET_VAL(message);
MS_ERROR_IF_NULL_WO_RET_VAL(iteration_);
MS_ERROR_IF_NULL_WO_RET_VAL(communicator_with_server_);
auto tcp_comm = std::dynamic_pointer_cast<ps::core::TcpCommunicator>(communicator_with_server_);
MS_ERROR_IF_NULL_WO_RET_VAL(tcp_comm);
std::string hyper_params_str(static_cast<const char *>(message->data()), message->len());
nlohmann::json new_instance_json;
nlohmann::json response;
try {
new_instance_json = nlohmann::json::parse(hyper_params_str);
} catch (const std::exception &e) {
response["result"] = false;
response["message"] = "The hyper-parameter data is not in json format.";
if (!tcp_comm->SendResponse(response.dump().c_str(), response.dump().size(), message)) {
MS_LOG(ERROR) << "Sending response failed.";
return;
}
}
std::string result_message = "";
bool result = iteration_->NewInstance(new_instance_json, &result_message);
response["result"] = result;
response["message"] = result_message;
if (!tcp_comm->SendResponse(response.dump().c_str(), response.dump().size(), message)) {
MS_LOG(ERROR) << "Sending response failed.";
return;
}
}
void Server::HandleQueryInstanceRequest(const std::shared_ptr<ps::core::MessageHandler> &message) {
MS_ERROR_IF_NULL_WO_RET_VAL(message);
nlohmann::json response;
response["start_fl_job_threshold"] = ps::PSContext::instance()->start_fl_job_threshold();
response["start_fl_job_time_window"] = ps::PSContext::instance()->start_fl_job_time_window();
response["update_model_ratio"] = ps::PSContext::instance()->update_model_ratio();
response["update_model_time_window"] = ps::PSContext::instance()->update_model_time_window();
response["fl_iteration_num"] = ps::PSContext::instance()->fl_iteration_num();
response["client_epoch_num"] = ps::PSContext::instance()->client_epoch_num();
response["client_batch_size"] = ps::PSContext::instance()->client_batch_size();
response["client_learning_rate"] = ps::PSContext::instance()->client_learning_rate();
auto tcp_comm = std::dynamic_pointer_cast<ps::core::TcpCommunicator>(communicator_with_server_);
MS_ERROR_IF_NULL_WO_RET_VAL(tcp_comm);
if (!tcp_comm->SendResponse(response.dump().c_str(), response.dump().size(), message)) {
MS_LOG(ERROR) << "Sending response failed.";
return;
}
}
} // namespace server
} // namespace fl
} // namespace mindspore

View File

@ -23,6 +23,7 @@
#include "ps/core/communicator/communicator_base.h"
#include "ps/core/communicator/tcp_communicator.h"
#include "ps/core/communicator/task_executor.h"
#include "ps/core/file_configuration.h"
#include "fl/server/common.h"
#include "fl/server/executor.h"
#include "fl/server/iteration.h"
@ -56,6 +57,9 @@ class Server {
void CancelSafeMode();
bool IsSafeMode() const;
// Whether the training job of the server is enabled.
InstanceState instance_state() const;
private:
Server()
: server_node_(nullptr),
@ -88,6 +92,9 @@ class Server {
// Load variables which is set by ps_context.
void InitServerContext();
// Try to recover server config from persistent storage.
void Recovery();
// Initialize the server cluster, server node and communicators.
void InitCluster();
bool InitCommunicatorWithServer();
@ -103,6 +110,9 @@ class Server {
// Register cluster exception callbacks. This method is called in RegisterCommCallbacks.
void RegisterExceptionEventCallback(const std::shared_ptr<ps::core::TcpCommunicator> &communicator);
// Register message callbacks. These messages are mainly from scheduler.
void RegisterMessageCallback(const std::shared_ptr<ps::core::TcpCommunicator> &communicator);
// Initialize executor according to the server mode.
void InitExecutor();
@ -112,6 +122,8 @@ class Server {
// Create round kernels and bind these kernels with corresponding Round.
void RegisterRoundKernel();
void InitMetrics();
// The communicators should be started after all initializations are completed.
void StartCommunicator();
@ -123,6 +135,16 @@ class Server {
void ProcessAfterScalingOut();
void ProcessAfterScalingIn();
// Handlers for enableFLS/disableFLS requests from the scheduler.
void HandleEnableServerRequest(const std::shared_ptr<ps::core::MessageHandler> &message);
void HandleDisableServerRequest(const std::shared_ptr<ps::core::MessageHandler> &message);
// Finish current instance and start a new one. FLPlan could be changed in this method.
void HandleNewInstanceRequest(const std::shared_ptr<ps::core::MessageHandler> &message);
// Query current instance information.
void HandleQueryInstanceRequest(const std::shared_ptr<ps::core::MessageHandler> &message);
// The server node is initialized in Server.
std::shared_ptr<ps::core::ServerNode> server_node_;

View File

@ -123,8 +123,9 @@ bool FLWorker::SendToServer(uint32_t server_rank, const void *data, size_t size,
return false;
}
if (std::string(reinterpret_cast<char *>((*output)->data()), (*output)->size()) == ps::kClusterSafeMode) {
MS_LOG(INFO) << "The server " << server_rank << " is in safemode.";
std::string response_str = std::string(reinterpret_cast<char *>((*output)->data()), (*output)->size());
if (response_str == ps::kClusterSafeMode || response_str == ps::kJobNotAvailable) {
MS_LOG(INFO) << "The server " << server_rank << " is in safemode or finished.";
std::this_thread::sleep_for(std::chrono::milliseconds(kWorkerRetryDurationForSafeMode));
} else {
break;

View File

@ -35,6 +35,7 @@ using FBBuilder = flatbuffers::FlatBufferBuilder;
// The step number for worker to judge whether to communicate with server.
constexpr uint32_t kTrainBeginStepNum = 1;
constexpr uint32_t kTrainEndStepNum = 0;
constexpr uint32_t kOneStepPerIteration = 1;
// The sleeping time of the worker thread before the networking is completed.
constexpr uint32_t kWorkerSleepTimeForNetworking = 1000;
@ -42,7 +43,7 @@ constexpr uint32_t kWorkerSleepTimeForNetworking = 1000;
// The time duration between retrying when server is in safemode.
constexpr uint32_t kWorkerRetryDurationForSafeMode = 500;
// The leader server rank.
// The rank of the leader server.
constexpr uint32_t kLeaderServerRank = 0;
enum class IterationState {

View File

@ -747,7 +747,8 @@ bool StartServerAction(const ResourcePtr &res) {
{"updateModel", true, update_model_time_window, true, update_model_threshold},
{"getModel"},
{"pullWeight"},
{"pushWeight", false, 3000, true, server_num, true}};
{"pushWeight", false, 3000, true, server_num, true},
{"pushMetrics", false, 3000, true, 1}};
float share_secrets_ratio = ps::PSContext::instance()->share_secrets_ratio();
uint64_t cipher_time_window = ps::PSContext::instance()->cipher_time_window();

View File

@ -250,6 +250,7 @@ using HandlerAfterScaleOut = std::function<void(void)>;
using HandlerAfterScaleIn = std::function<void(void)>;
constexpr char kClusterSafeMode[] = "The cluster is in safemode.";
constexpr char kJobNotAvailable[] = "The server's training job is disabled or finished.";
enum class CustomEvent { kIterationRunning = 0, kIterationCompleted };