From 273360866d95a44f302c50856c4dfd8bc7f82629 Mon Sep 17 00:00:00 2001 From: PathikritGhosh Date: Thu, 28 Apr 2022 21:52:01 -0500 Subject: [PATCH] TreeLB changes --- CMakeLists.txt | 2 +- cmake/converse.cmake | 1 + src/ck-ldb/LBManager.C | 2 + src/ck-ldb/LBManager.h | 3 + src/ck-ldb/MetaBalancer.C | 29 +- src/ck-ldb/MetaBalancer.h | 6 +- src/ck-ldb/TreeLB.C | 1 + src/ck-ldb/TreeLB.h | 26 + src/ck-ldb/TreeLevel.h | 363 +++- src/ck-ldb/XGBoost/model/model.bin | Bin 0 -> 157594 bytes src/ck-ldb/XGBoost/model/model.txt | 2108 +++++++++++++++++++++++ src/ck-ldb/XGBoost/model/model.xml | 1 + src/ck-ldb/XGBoost/model_tree/model.bin | Bin 0 -> 75410 bytes src/ck-ldb/XGBoost/model_tree/model.txt | 1114 ++++++++++++ src/ck-ldb/XGBoost/model_tree/model.xml | 1 + src/scripts/Makefile | 2 +- src/util/fastforest.C | 397 +++++ src/util/fastforest.h | 123 ++ 18 files changed, 4168 insertions(+), 11 deletions(-) create mode 100644 src/ck-ldb/XGBoost/model/model.bin create mode 100644 src/ck-ldb/XGBoost/model/model.txt create mode 100644 src/ck-ldb/XGBoost/model/model.xml create mode 100644 src/ck-ldb/XGBoost/model_tree/model.bin create mode 100644 src/ck-ldb/XGBoost/model_tree/model.txt create mode 100644 src/ck-ldb/XGBoost/model_tree/model.xml create mode 100644 src/util/fastforest.C create mode 100644 src/util/fastforest.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cd3a520d70..9e38f9de1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -779,7 +779,7 @@ set(src-util-h-sources src/util/SSE-Double.h src/util/SSE-Float.h src/util/treeStrategy_nodeAware_minBytes.h src/util/treeStrategy_nodeAware_minGens.h src/util/treeStrategy_topoUnaware.h src/util/uFcontext.h src/util/uJcontext.h src/util/valgrind.h - src/util/vector2d.h) + src/util/vector2d.h src/util/fastforest.h) foreach(filename ${src-util-h-sources}) configure_file(${filename} include/ COPYONLY) diff --git a/cmake/converse.cmake b/cmake/converse.cmake index 857f2ac0b5..80e85bbe5c 100644 --- a/cmake/converse.cmake +++ b/cmake/converse.cmake @@ -155,6 +155,7 @@ set(conv-util-cxx-sources src/util/pup_util.C src/util/pup_xlater.C src/util/spanningTree.C + src/util/fastforest.C ) if(CMK_CAN_LINK_FORTRAN) diff --git a/src/ck-ldb/LBManager.C b/src/ck-ldb/LBManager.C index af85060423..f240e93d4a 100644 --- a/src/ck-ldb/LBManager.C +++ b/src/ck-ldb/LBManager.C @@ -198,6 +198,8 @@ void _loadbalancerInit() CmiGetArgStringDesc(argv, "+MetaLBModelDir", &_lb_args.metaLbModelDir(), "Use this directory to read model for MetaLB"); + _lb_args.treeMetaLbOn() = CmiGetArgFlagDesc(argv, "+TreeMetaLB", "use MetaLB within TreeLB"); + if (_lb_args.metaLbOn() && _lb_args.metaLbModelDir() != nullptr) { #if CMK_USE_ZLIB diff --git a/src/ck-ldb/LBManager.h b/src/ck-ldb/LBManager.h index 8768f4aaff..4d3931b184 100644 --- a/src/ck-ldb/LBManager.h +++ b/src/ck-ldb/LBManager.h @@ -40,6 +40,7 @@ class CkLBArgs double _lb_targetRatio; // Specifies the target load ratio for LBs that aim for a // particular load ratio bool _lb_metaLbOn; + bool _lb_treeMetaLbOn; char* _lb_metaLbModelDir; char* _lb_treeLBFile = (char*)"treelb.json"; @@ -56,6 +57,7 @@ class CkLBArgs _lb_maxDistPhases = 10; _lb_targetRatio = 1.05; _lb_metaLbOn = false; + _lb_treeMetaLbOn = false; _lb_metaLbModelDir = nullptr; } inline char*& treeLBFile() { return _lb_treeLBFile; } @@ -78,6 +80,7 @@ class CkLBArgs inline int& maxDistPhases() { return _lb_maxDistPhases; } inline double& targetRatio() { return _lb_targetRatio; } inline bool& metaLbOn() { return _lb_metaLbOn; } + inline bool& treeMetaLbOn() {return _lb_treeMetaLbOn; } inline char*& metaLbModelDir() { return _lb_metaLbModelDir; } }; diff --git a/src/ck-ldb/MetaBalancer.C b/src/ck-ldb/MetaBalancer.C index 531e41ca9b..df6c8f7eee 100644 --- a/src/ck-ldb/MetaBalancer.C +++ b/src/ck-ldb/MetaBalancer.C @@ -23,6 +23,7 @@ #define NEGLECT_IDLE 2 // Should never be == 1 #define MIN_STATS 6 #define STATS_COUNT 29 // The number of stats collected during reduction +#define CLASSES 6 #define MAXDOUBLE std::numeric_limits::max() @@ -36,6 +37,7 @@ using std::max; CkReductionMsg* lbDataCollection(int nMsg, CkReductionMsg** msgs) { double *lb_data; lb_data = (double*)msgs[0]->getData(); + lb_data = (double*)msgs[0]->getData(); for (int i = 1; i < nMsg; i++) { CkAssert(msgs[i]->getSize() == STATS_COUNT*sizeof(double)); if (msgs[i]->getSize() != STATS_COUNT*sizeof(double)) { @@ -178,6 +180,12 @@ void MetaBalancer::init(void) { srand(time(NULL)); rFmodel = new ForestModel; rFmodel->readModel(_lb_args.metaLbModelDir()); + + std::vector features{"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", + "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "f21", "f22", + "f23", "f24"}; + xgboost = fastforest::load_txt("model/model.txt", features, CLASSES); + } } } @@ -295,9 +303,9 @@ bool MetaBalancer::AddLoad(int it_n, double load) { int index = it_n % VEC_SIZE; total_count_vec[index]++; adaptive_struct.total_syncs_called++; - CkPrintf("At PE %d Total contribution for iteration %d is %d \ + DEBAD(("At PE %d Total contribution for iteration %d is %d \ total objs %d\n", CkMyPe(), it_n, total_count_vec[index], - lbmanager->GetObjDataSz()); + lbmanager->GetObjDataSz())); if (it_n <= adaptive_struct.finished_iteration_no) { CkAbort("Error!! Received load for iteration that has contributed\n"); @@ -324,6 +332,7 @@ bool MetaBalancer::AddLoad(int it_n, double load) { return true; } +//NOTE: Data Collection void MetaBalancer::ContributeStats(int it_n) { #if CMK_LBDB_ON int index = it_n % VEC_SIZE; @@ -493,7 +502,7 @@ void MetaBalancer::ReceiveMinStats(double *load, int n) { // avg_hops, avg_hop_bytes, _lb_args.alpha(), _lb_args.beta(), // app_iteration_time); - DEBAD( + DEBAD( ("Features:%lf %lf %lf %lf %lf %lf %lf %lf \ %lf %lf %lf %lf %lf %lf %lf %lf %lf \ %lf %lf %lf %lf %lf %lf %lf %d %lf\n", @@ -532,10 +541,24 @@ void MetaBalancer::ReceiveMinStats(double *load, int n) { avg_hops, avg_hop_Kbytes, comm_comp_ratio}; + + //Note:Predict LB // Model returns value [1,num_lbs] int predicted_lb = rFmodel->forestTest(test_data, 1, 26); DEBAD(("***********Final classification = %d *****************\n", predicted_lb)); + std::vector prob = xgboost.softmax(test_data.data()); + int cur_pred = 1; + double cur_prob = 0.0; + for(int i = 0; i < prob.size(); ++i) { + if(prob[i] > cur_prob) { + cur_prob = prob[i]; + cur_pred = i+1; + } + } + + predicted_lb = cur_pred; + // predicted_lb-1 since predicted_lb class count in the model starts at 1 thisProxy.MetaLBSetLBOnChares(current_balancer, predicted_lb - 1); current_balancer = predicted_lb - 1; diff --git a/src/ck-ldb/MetaBalancer.h b/src/ck-ldb/MetaBalancer.h index 1af88f8472..80fafc698b 100644 --- a/src/ck-ldb/MetaBalancer.h +++ b/src/ck-ldb/MetaBalancer.h @@ -32,6 +32,7 @@ #include "LBManager.h" #include "RandomForestModel.h" +#include "fastforest.h" #include #include "MetaBalancer.decl.h" @@ -89,7 +90,9 @@ class MetaBalancer : public CBase_MetaBalancer { MetaBalancer(void) : rFmodel(NULL) { init(); } MetaBalancer(CkMigrateMessage* m) : CBase_MetaBalancer(m) { init(); } ~MetaBalancer() { - if (CkMyPe() == 0) delete rFmodel; + if (CkMyPe() == 0) { + delete rFmodel; + } } private: @@ -176,6 +179,7 @@ class MetaBalancer : public CBase_MetaBalancer { int total_ovld_pes; int current_balancer; ForestModel* rFmodel; + fastforest::FastForest xgboost; struct AdaptiveData { double iteration; diff --git a/src/ck-ldb/TreeLB.C b/src/ck-ldb/TreeLB.C index a5dcb251a5..892ae625c7 100644 --- a/src/ck-ldb/TreeLB.C +++ b/src/ck-ldb/TreeLB.C @@ -136,6 +136,7 @@ TreeLB::~TreeLB() #endif } +//Note: Each level configured with choice of load balancer void TreeLB::configure(LBTreeBuilder& builder, json& config) { #if CMK_LBDB_ON diff --git a/src/ck-ldb/TreeLB.h b/src/ck-ldb/TreeLB.h index 31445f5dc3..483e0fc9c7 100644 --- a/src/ck-ldb/TreeLB.h +++ b/src/ck-ldb/TreeLB.h @@ -6,12 +6,18 @@ #include "BaseLB.h" #include "TreeLB.decl.h" #include "json.hpp" +#include "fastforest.h" #include using json = nlohmann::json; +//using namespace rfmodel; #define DEBUG__TREE_LB_L1 0 #define DEBUG__TREE_LB_L2 0 #define DEBUG__TREE_LB_L3 0 +#define STATS_COUNT 29 +#define CLASSES 3 + +extern CkLBArgs _lb_args; void CreateTreeLB(); @@ -24,12 +30,28 @@ class TreeLBMessage { public: uint8_t level; + //TODO Metabalancer stats + double lb_data[STATS_COUNT]; // WARNING: don't add any virtual methods here }; class LevelLogic { public: + + //TODO: Initialize if only Metabalancer called + LevelLogic() { + //rfModel = new ForestModel; + //rfmodel->readModel(_lb_args.metaLbModelDir()); + if(_lb_args.treeMetaLbOn()) { + std::vector features{"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", + "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "f21", "f22", + "f23", "f24"}; + xgboost = fastforest::load_txt("model_tree/model.txt", features, CLASSES); + } + } + + virtual ~LevelLogic() {} /// return msg with lb stats for this PE. only needed at leaves @@ -125,6 +147,9 @@ class LevelLogic protected: std::vector stats_msgs; + //TODO Meta model + //ForestModel* rfmodel; + fastforest::FastForest xgboost; }; class LBTreeBuilder; @@ -140,6 +165,7 @@ class TreeLB : public CBase_TreeLB loadConfigFile(opts); init(opts); } + TreeLB(CkMigrateMessage* m) : CBase_TreeLB(m) {} virtual ~TreeLB(); diff --git a/src/ck-ldb/TreeLevel.h b/src/ck-ldb/TreeLevel.h index 6da14abdd7..e3722da50a 100644 --- a/src/ck-ldb/TreeLevel.h +++ b/src/ck-ldb/TreeLevel.h @@ -9,12 +9,52 @@ #include "TreeStrategyFactory.h" #include #include // std::numeric_limits +#include "json_fwd.hpp" +#include "json.hpp" #define FLOAT_TO_INT_MULT 10000 +//#define STATS_COUNT 29 +//#define CLASSES 3 // ----------------------- msgs ----------------------- #include "TreeLevel.decl.h" +using namespace TreeStrategy; +using json = nlohmann::json; + +//TODO: Metabalancer match index with appropriate lb type +/*enum metalb_stats_types{ + ITER_NO, + NUM_PROCS, + TOTAL_LOAD, + MAX_LOAD, + IDLE_TIME, + UTILIZATION, + TOTAL_LOAD_W_BG, + MAX_LOAD_W_BG, + TOTAL_KBYTES, + TOTAL_KMSGS, + WITHIN_PE_KBYTES, + OUTSIDE_PE_KBYTES, + SUM_COMM_NEIGHBORS, + MAX_COMM_NEIGHBORS, + SUM_OBJ_COUNT, + MAX_OBJ_COUNT, + SUM_OBJ_LOAD, + MAX_OBJ_LOAD, + SUM_HOPS, + SUM_HOP_KBYTES, + LOAD_STDEV2, + MAX_UTIL, + MIN_LOAD, + MIN_BG, + MIN_OBJ_LOAD, + MAX_ITER_TIME, + LOAD_SKEWNESS, + LOAD_KURTOSIS, + TOTAL_OVERLOADED_PES, +};*/ +std::string LB[CLASSES] = {"Greedy", "RefineA", "GreedyRefine"}; class LLBMigrateMsg : public TreeLBMessage, public CMessage_LLBMigrateMsg { @@ -44,6 +84,7 @@ class LBStatsMsg_1 : public TreeLBMessage, public CMessage_LBStatsMsg_1 unsigned int* order; // list of obj ids sorted by load (ids are determined by position in oloads) + //Metebalancer statistics merge static TreeLBMessage* merge(std::vector& msgs) { // TODO ideally have option of sorting objects @@ -72,9 +113,8 @@ class LBStatsMsg_1 : public TreeLBMessage, public CMessage_LBStatsMsg_1 newMsg->nPes = nPes; int pe_cnt = 0; int obj_cnt = 0; - for (int i = 0; i < msgs.size(); i++) - { - LBStatsMsg_1* msg = (LBStatsMsg_1*)msgs[i]; + for (int i = 0; i < msgs.size(); i++) { + LBStatsMsg_1 *msg = (LBStatsMsg_1 *) msgs[i]; const int msg_npes = msg->nPes; memcpy(newMsg->pe_ids + pe_cnt, msg->pe_ids, sizeof(int) * msg_npes); memcpy(newMsg->bgloads + pe_cnt, msg->bgloads, sizeof(float) * msg_npes); @@ -87,12 +127,210 @@ class LBStatsMsg_1 : public TreeLBMessage, public CMessage_LBStatsMsg_1 obj_cnt += msg->nObjs; pe_cnt += msg_npes; + + //TODO Meta merge meta stats + if (_lb_args.treeMetaLbOn()) { + newMsg->lb_data[NUM_PROCS] += msg[i].lb_data[NUM_PROCS]; + + newMsg->lb_data[TOTAL_LOAD] += msg[i].lb_data[TOTAL_LOAD]; + newMsg->lb_data[MAX_LOAD] = fmax(newMsg->lb_data[MAX_LOAD], msg[i].lb_data[MAX_LOAD]); + newMsg->lb_data[MIN_LOAD] = fmin(newMsg->lb_data[MIN_LOAD], msg[i].lb_data[MIN_LOAD]); + + newMsg->lb_data[IDLE_TIME] += msg[i].lb_data[IDLE_TIME]; + //Minimum utilization + newMsg->lb_data[UTILIZATION] = fmin(newMsg->lb_data[UTILIZATION], msg[i].lb_data[UTILIZATION]); + newMsg->lb_data[MAX_UTIL] = fmax(newMsg->lb_data[MAX_UTIL], msg[i].lb_data[MAX_UTIL]); + newMsg->lb_data[TOTAL_LOAD_W_BG] += msg[i].lb_data[TOTAL_LOAD_W_BG]; + newMsg->lb_data[MIN_BG] = fmin(newMsg->lb_data[MIN_BG], msg[i].lb_data[MIN_BG]); + newMsg->lb_data[MAX_LOAD_W_BG] = fmax(newMsg->lb_data[MAX_LOAD_W_BG], msg[i].lb_data[MAX_LOAD_W_BG]); + + newMsg->lb_data[TOTAL_KBYTES] += msg[i].lb_data[TOTAL_KBYTES]; + newMsg->lb_data[TOTAL_KMSGS] += msg[i].lb_data[TOTAL_KMSGS]; + newMsg->lb_data[WITHIN_PE_KBYTES] += msg[i].lb_data[WITHIN_PE_KBYTES]; + newMsg->lb_data[OUTSIDE_PE_KBYTES] += msg[i].lb_data[OUTSIDE_PE_KBYTES]; + newMsg->lb_data[SUM_COMM_NEIGHBORS] += msg[i].lb_data[SUM_COMM_NEIGHBORS]; + newMsg->lb_data[MAX_COMM_NEIGHBORS] = fmax(msg[i].lb_data[MAX_COMM_NEIGHBORS], + newMsg->lb_data[MAX_COMM_NEIGHBORS]); + + newMsg->lb_data[SUM_OBJ_COUNT] += msg[i].lb_data[SUM_OBJ_COUNT]; + newMsg->lb_data[MAX_OBJ_COUNT] = fmax(msg[i].lb_data[MAX_OBJ_COUNT], newMsg->lb_data[MAX_OBJ_COUNT]); + newMsg->lb_data[MIN_OBJ_LOAD] = fmin(msg[i].lb_data[MIN_OBJ_LOAD], newMsg->lb_data[MIN_OBJ_LOAD]); + newMsg->lb_data[SUM_OBJ_LOAD] += msg[i].lb_data[SUM_OBJ_LOAD]; + newMsg->lb_data[MAX_OBJ_LOAD] = fmax(msg[i].lb_data[MAX_OBJ_LOAD], newMsg->lb_data[MAX_OBJ_LOAD]); + newMsg->lb_data[SUM_HOPS] += msg[i].lb_data[SUM_HOPS]; + newMsg->lb_data[SUM_HOP_KBYTES] += msg[i].lb_data[SUM_HOP_KBYTES]; + newMsg->lb_data[MAX_ITER_TIME] = fmax(msg[i].lb_data[MAX_ITER_TIME], newMsg->lb_data[MAX_ITER_TIME]); + + newMsg->lb_data[LOAD_STDEV2] += msg[i].lb_data[LOAD_STDEV2]; + newMsg->lb_data[LOAD_SKEWNESS] += msg[i].lb_data[LOAD_SKEWNESS]; + newMsg->lb_data[LOAD_KURTOSIS] += msg[i].lb_data[LOAD_KURTOSIS]; + newMsg->lb_data[TOTAL_OVERLOADED_PES] += msg[i].lb_data[TOTAL_OVERLOADED_PES]; + } } newMsg->obj_start[pe_cnt] = obj_cnt; return newMsg; } + //TODO Meta Predict load balancer based on statistics for metabalancer + static int getPredictedLB_XG(TreeLBMessage* msg, fastforest::FastForest xgboost) { + double pe_count = msg->lb_data[NUM_PROCS]; + double avg_load = msg->lb_data[TOTAL_LOAD]/msg->lb_data[NUM_PROCS]; + double max_load = msg->lb_data[MAX_LOAD]; + double min_load = msg->lb_data[MIN_LOAD]; + double avg_utilization = msg->lb_data[IDLE_TIME]/msg->lb_data[NUM_PROCS]; + double min_utilization = msg->lb_data[UTILIZATION]; + int iteration_n = (int) msg->lb_data[ITER_NO]; + double avg_load_bg = msg->lb_data[TOTAL_LOAD_W_BG]/msg->lb_data[NUM_PROCS]; + double min_load_bg = msg->lb_data[MIN_BG]; + double max_load_bg = msg->lb_data[MAX_LOAD_W_BG]; + int total_objs = (int) msg->lb_data[SUM_OBJ_COUNT]; + double total_Kbytes = msg->lb_data[TOTAL_KBYTES]; + double total_Kmsgs = msg->lb_data[TOTAL_KMSGS]; + double total_outsidepeKmsgs = msg->lb_data[WITHIN_PE_KBYTES]; + double total_outsidepeKbytes = msg->lb_data[OUTSIDE_PE_KBYTES]; + double avg_bg = avg_load_bg - avg_load; + double avg_comm_neighbors = msg->lb_data[SUM_COMM_NEIGHBORS]/total_objs; + double max_comm_neighbors = msg->lb_data[MAX_COMM_NEIGHBORS]; + double avg_obj_load = msg->lb_data[SUM_OBJ_LOAD]/total_objs; + double min_obj_load = msg->lb_data[MIN_OBJ_LOAD]; + double max_obj_load = msg->lb_data[MAX_OBJ_LOAD]; + double avg_hops = msg->lb_data[SUM_HOPS]/(total_Kmsgs*1024.0); // The messages are in K + double avg_hop_Kbytes = msg->lb_data[SUM_HOP_KBYTES]/(total_Kmsgs*1024.0); + double standard_dev = sqrt(msg->lb_data[LOAD_STDEV2]/msg->lb_data[NUM_PROCS]); + double skewness = msg->lb_data[LOAD_SKEWNESS]/(msg->lb_data[NUM_PROCS] * standard_dev * standard_dev * + standard_dev); + double kurtosis = msg->lb_data[LOAD_KURTOSIS]/(msg->lb_data[NUM_PROCS] * standard_dev * standard_dev * + standard_dev * standard_dev) - 3; + int ovld_pes = (int) msg->lb_data[TOTAL_OVERLOADED_PES]; + double max_utilization = msg->lb_data[MAX_UTIL]; + double app_iteration_time = msg->lb_data[MAX_ITER_TIME]; + + // Features to be output + double pe_imbalance = max_load/avg_load; + double pe_load_std_frac = standard_dev/avg_load; + double pe_with_bg_imb = max_load_bg/avg_load_bg; + double bg_load_frac = avg_bg/avg_load; + double pe_gain = max_load - avg_load; + double mslope = max_load - min_load; + double aslope = avg_load - min_load; + double internal_bytes_frac = (total_Kbytes-total_outsidepeKbytes)/total_Kbytes; + double comm_comp_ratio = (_lb_args.alpha()*total_Kmsgs+_lb_args.beta()*total_Kbytes)/(avg_load*pe_count); + + std::vector test_data{pe_imbalance, + pe_load_std_frac, + pe_with_bg_imb, + 0, + bg_load_frac, + pe_gain, + avg_utilization, + min_utilization, + max_utilization, + avg_obj_load, + min_obj_load, + max_obj_load, + total_objs / pe_count, + pe_count, + total_Kbytes, + total_Kmsgs, + total_outsidepeKbytes / total_Kbytes, + total_outsidepeKmsgs / total_Kmsgs, + internal_bytes_frac, + (total_Kbytes - total_outsidepeKbytes) / total_Kmsgs, + avg_comm_neighbors, + mslope, + aslope, + avg_hops, + avg_hop_Kbytes, + comm_comp_ratio}; + + + std::vector prob = xgboost.softmax(test_data.data()); + int cur_pred = 0; + double cur_prob = 0.0; + for(int i = 0; i < prob.size(); ++i) { + if(prob[i] > cur_prob) { + cur_prob = prob[i]; + cur_pred = i; + } + } + return cur_pred; + } + + //TODO Meta Predict load balancer based on statistics for metabalancer + /* + static int getPredictedLB(TreeLBMessage* msg, ForestModel* rfmodel) { + double pe_count = msg->lb_data[NUM_PROCS]; + double avg_load = msg->lb_data[TOTAL_LOAD]/msg->lb_data[NUM_PROCS]; + double max_load = msg->lb_data[MAX_LOAD]; + double min_load = msg->lb_data[MIN_LOAD]; + double avg_utilization = msg->lb_data[IDLE_TIME]/msg->lb_data[NUM_PROCS]; + double min_utilization = msg->lb_data[UTILIZATION]; + int iteration_n = (int) msg->lb_data[ITER_NO]; + double avg_load_bg = msg->lb_data[TOTAL_LOAD_W_BG]/msg->lb_data[NUM_PROCS]; + double min_load_bg = msg->lb_data[MIN_BG]; + double max_load_bg = msg->lb_data[MAX_LOAD_W_BG]; + int total_objs = (int) msg->lb_data[SUM_OBJ_COUNT]; + double total_Kbytes = msg->lb_data[TOTAL_KBYTES]; + double total_Kmsgs = msg->lb_data[TOTAL_KMSGS]; + double total_outsidepeKmsgs = msg->lb_data[WITHIN_PE_KBYTES]; + double total_outsidepeKbytes = msg->lb_data[OUTSIDE_PE_KBYTES]; + double avg_bg = avg_load_bg - avg_load; + double avg_comm_neighbors = msg->lb_data[SUM_COMM_NEIGHBORS]/total_objs; + double max_comm_neighbors = msg->lb_data[MAX_COMM_NEIGHBORS]; + double avg_obj_load = msg->lb_data[SUM_OBJ_LOAD]/total_objs; + double min_obj_load = msg->lb_data[MIN_OBJ_LOAD]; + double max_obj_load = msg->lb_data[MAX_OBJ_LOAD]; + double avg_hops = msg->lb_data[SUM_HOPS]/(total_Kmsgs*1024.0); // The messages are in K + double avg_hop_Kbytes = msg->lb_data[SUM_HOP_KBYTES]/(total_Kmsgs*1024.0); + double standard_dev = sqrt(load[LOAD_STDEV2]/msg->lb_data[NUM_PROCS]); + double skewness = msg->lb_data[LOAD_SKEWNESS]/(msg->lb_data[NUM_PROCS] * standard_dev * standard_dev * + standard_dev); + double kurtosis = msg->lb_data[LOAD_KURTOSIS]/(msg->lb_data[NUM_PROCS] * standard_dev * standard_dev * + standard_dev * standard_dev) - 3; + int ovld_pes = (int) msg->lb_data[TOTAL_OVERLOADED_PES]; + double max_utilization = msg->lb_data[MAX_UTIL]; + double app_iteration_time = msg->lb_data[MAX_ITER_TIME]; + + // Features to be output + double pe_imbalance = max_load/avg_load; + double pe_load_std_frac = standard_dev/avg_load; + double pe_with_bg_imb = max_load_bg/avg_load_bg; + double bg_load_frac = avg_bg/avg_load; + double pe_gain = max_load - avg_load; + double internal_bytes_frac = (total_Kbytes-total_outsidepeKbytes)/total_Kbytes; + double comm_comp_ratio = (_lb_args.alpha()*total_Kmsgs+_lb_args.beta()*total_Kbytes)/(avg_load*pe_count); + + std::vector test_data{pe_imbalance, + pe_load_std_frac, + pe_with_bg_imb, + 0, + bg_load_frac, + pe_gain, + avg_utilization, + min_utilization, + max_utilization, + avg_obj_load, + min_obj_load, + max_obj_load, + total_objs / pe_count, + pe_count, + total_Kbytes, + total_Kmsgs, + total_outsidepeKbytes / total_Kbytes, + total_outsidepeKmsgs / total_Kmsgs, + internal_bytes_frac, + (total_Kbytes - total_outsidepeKbytes) / total_Kmsgs, + avg_comm_neighbors, + mslope, + aslope, + avg_hops, + avg_hop_Kbytes, + comm_comp_ratio}; + return rfmodel->forestTest(test_data, 1, 26); + } + */ + template static float fill(std::vector msgs, std::vector& objs, std::vector

& procs, LLBMigrateMsg* migMsg, @@ -470,7 +708,7 @@ class RootLevel : public LevelLogic json& config, bool repeat_strategies = false, bool token_passing = true) { - using namespace TreeStrategy; + //using namespace TreeStrategy; for (auto w : wrappers) delete w; wrappers.clear(); if (num_groups == -1) @@ -524,6 +762,21 @@ class RootLevel : public LevelLogic num_children, nPes, nObjs); #endif + + //TODO: If metabalancer LB called + //string predicted_lb = LB[LBStatsMsg_1::getPredictedLB(LBStatsMsg_1::fill(stats_msgs), rfmodel)]; + if(_lb_args.treeMetaLbOn()) { + std::string predicted_lb = LB[LBStatsMsg_1::getPredictedLB_XG(LBStatsMsg_1::merge(stats_msgs), xgboost)]; + + //TODO: metabalancer Initialize LB and add to wrappers + json config; + config["tolerance"] = 1.1; + this->repeat_strategies = false; + + wrappers.push_back(new StrategyWrapper, Proc<1, false>>( + predicted_lb, true, config + )); + } if (num_groups == -1) { // msg has object loads @@ -848,6 +1101,21 @@ class NodeSetLevel : public LevelLogic virtual TreeLBMessage* loadBalance(IDM& idm) { + if(_lb_args.treeMetaLbOn()) { + //TODO: If metabalancer LB called + //string predicted_lb = LB[LBStatsMsg_1::getPredictedLB(LBStatsMsg_1::fill(stats_msgs), rfmodel)]; + std::string predicted_lb = LB[LBStatsMsg_1::getPredictedLB_XG(LBStatsMsg_1::merge(stats_msgs), xgboost)]; + //TODO: Initialize LB & Add to wrappers + json config; + config["tolerance"] = 1.1; + this->repeat_strategies = false; + + wrappers.push_back(new StrategyWrapper, Proc<1, false>>( + predicted_lb, false, config + )); + current_strategy = wrappers.size() - 1; + } + CkAssert(wrappers.size() > current_strategy); IStrategyWrapper* wrapper = wrappers[current_strategy]; CkAssert(wrapper != nullptr); @@ -988,6 +1256,22 @@ class NodeLevel : public LevelLogic protected: LLBMigrateMsg* withinNodeLoadBalance() { + + if(_lb_args.treeMetaLbOn()) { + //TODO: If meta LB called + //string predicted_lb = LB[LBStatsMsg_1::getPredictedLB(LBStatsMsg_1::fill(stats_msgs), rfmodel)]; + std::string predicted_lb = LB[LBStatsMsg_1::getPredictedLB_XG(LBStatsMsg_1::merge(stats_msgs), xgboost)]; + //TODO: Initialize LB Add to wrappers + json config; + config["tolerance"] = 1.1; + this->repeat_strategies = false; + + wrappers.push_back(new StrategyWrapper, Proc<1, false>>( + predicted_lb, false, config + )); + current_strategy = wrappers.size() - 1; + } + CkAssert(wrappers.size() > current_strategy); IStrategyWrapper* wrapper = wrappers[current_strategy]; CkAssert(wrapper != nullptr); @@ -1046,6 +1330,11 @@ class NodeLevel : public LevelLogic class PELevel : public LevelLogic { + + double prev_idle; + double prev_load; + int itn; + public: struct LDObjLoadGreater { @@ -1055,7 +1344,11 @@ class PELevel : public LevelLogic } }; - PELevel(LBManager* _lbmgr) : lbmgr(_lbmgr), rateAware(_lb_args.testPeSpeed()) {} + PELevel(LBManager* _lbmgr) : lbmgr(_lbmgr), rateAware(_lb_args.testPeSpeed()) { + prev_idle = 0; + prev_load = 0; + itn = 0; + } virtual ~PELevel() {} @@ -1136,6 +1429,66 @@ class PELevel : public LevelLogic msg->bgloads[0] = float(bg_walltime); // fprintf(stderr, "[%d] my bgload is %f %f\n", mype, msg->bgloads[0], bg_walltime); + + if(_lb_args.treeMetaLbOn()) { + //TODO Meta Stats initialization + double idle_time, cpu_bgtime, load, prev_avg_load; + double bg_walltimed; + lbmgr->TotalTime(&idle_time, &load); + lbmgr->BackgroundLoad(&bg_walltimed, &cpu_bgtime); + idle_time -= prev_idle; + load -= prev_load; + + prev_avg_load = prev_load / itn; + prev_idle += idle_time; + prev_load += load; + itn++; + + + int bytes, msgs, outsidepemsgs, outsidepebytes, num_nghbors, hops, hopbytes; + bytes = msgs = outsidepemsgs = outsidepebytes = num_nghbors = hops = hopbytes = 0; + if (_lb_args.traceComm()) { + lbmgr->GetCommInfo(bytes, msgs, outsidepemsgs, + outsidepebytes, num_nghbors, hops, hopbytes); + } + + //TODO + int sync_for_bg = itn; + bg_walltimed = bg_walltimed * lbmgr->GetObjDataSz() / sync_for_bg; + + msg->lb_data[NUM_PROCS] = 1; + msg->lb_data[TOTAL_LOAD] = msg->lb_data[MAX_LOAD] = msg->lb_data[MIN_LOAD] = load; + if (load == 0.0) { + msg->lb_data[IDLE_TIME] = msg->lb_data[UTILIZATION] = msg->lb_data[MAX_UTIL] = 0.0; + } else { + msg->lb_data[IDLE_TIME] = msg->lb_data[UTILIZATION] = msg->lb_data[MAX_UTIL] = load / (idle_time + load); + } + + msg->lb_data[TOTAL_LOAD_W_BG] = msg->lb_data[MIN_BG] = msg->lb_data[MAX_LOAD_W_BG] = + msg->lb_data[TOTAL_LOAD] + bg_walltimed; + msg->lb_data[TOTAL_KBYTES] = ((double) bytes / 1024.0); + msg->lb_data[TOTAL_KMSGS] = ((double) msgs / 1024.0); + msg->lb_data[WITHIN_PE_KBYTES] = ((double) outsidepemsgs / 1024.0); + msg->lb_data[OUTSIDE_PE_KBYTES] = ((double) outsidepebytes / 1024.0); + msg->lb_data[SUM_COMM_NEIGHBORS] = msg->lb_data[MAX_COMM_NEIGHBORS] = num_nghbors; + msg->lb_data[SUM_OBJ_COUNT] = msg->lb_data[MAX_OBJ_COUNT] = lbmgr->GetObjDataSz(); + msg->lb_data[SUM_OBJ_LOAD] = msg->lb_data[MAX_OBJ_LOAD] = msg->lb_data[MIN_OBJ_LOAD] = load; + msg->lb_data[LOAD_STDEV2] = (load - prev_avg_load) * + (load - prev_avg_load); + msg->lb_data[LOAD_SKEWNESS] = (load - prev_avg_load) * + (load - prev_avg_load) * + (load - prev_avg_load); + msg->lb_data[LOAD_KURTOSIS] = msg->lb_data[LOAD_STDEV2] * msg->lb_data[LOAD_STDEV2]; + msg->lb_data[TOTAL_OVERLOADED_PES] = (load > prev_avg_load) ? 1 : 0; + msg->lb_data[SUM_HOPS] = 0; + msg->lb_data[SUM_HOP_KBYTES] = 0; + if (msgs > 0) { + msg->lb_data[SUM_HOPS] = (double) hops; + msg->lb_data[SUM_HOP_KBYTES] = ((double) hopbytes / 1024.0); + } + msg->lb_data[MAX_ITER_TIME] = load + idle_time; + } + return msg; } diff --git a/src/ck-ldb/XGBoost/model/model.bin b/src/ck-ldb/XGBoost/model/model.bin new file mode 100644 index 0000000000000000000000000000000000000000..d04cd2899eef2f0bed5af89fb8a8d66e33aebcd1 GIT binary patch literal 157594 zcmd?S2UHYI*EI@=0Z@V<0%F31Ig=)wLQyeC%$Nh_obXWxkf2BwK~NM>0Rm=o|%Reu!2JI=cITkEd>>3(moCDT1K;Mr%Nv(KsO>h47=)-F?0QUXKi zkLJ>U%F=&$!>>)r-v7NHn*Z}(t=Y1A^9rj?>y~X^zj587X8-f<@J&(p?dLA8**cusLM{{K~w;yl1;x zJ0omyD7?}Wqfr;8W#G^8ma^s%#D|N8%#o7P4og?G?RFLUobC9fF)a#AzJYCTgnyd% zK!QgWzkx_M-<3%DY~-iX1LFX5Ec-p|y9w6%);Lby&bd$ij;QKA#A$h<(R#W_ z`gO8NKL?$Z1mY+B#>DKgXwjBlhd&!MY{6u)fT;NQXcpSU&=;(adQcX*pChyVY}JYutsN+}Zk4L{}@^C!zcj)}kV zFUvRn9ocx`D*yj~!LO)}uAFyCj{mJ))H1RK|W_Y|G17OHoJ;#Z=`?SkB%UCWaD3Nt7b+=>(?;OE)Ff}HJISTcxOHJgky1C zNxi_wTkQ9vt?S-X{9LS~ON1K<9$EahpNr(`sQa}SLd>!3_x3F6CU|HrLQC&9vF_{Z zLjKMVm28Na_y~Qv6-E8zx;g=Lj-R1^9IcduF73&rpYIj~g5GU4!UNmmsI4@9KD=Is zZEX}s?h{%Oe6s$TGZ_g6rT-94Dv3sVDkmH88}CKWoh#|F(HDQXI=mD9o2;*+(i$g9 z(|U$AqY?g`<70{aviMs~)XH#+{|I^oacJ7)p%ibgJMIvEt|#Sx^c_#srsMeznR-GL*;fdQSw^k0%kl81s%24|Fq}@dw-aS5!xPH$NkX zU$wJJ#^?dl;CE>RdgEbC`L-zi2|r!EDgN0-w&>jR2jp|M<5yHihh&=)JhJh>@>uhX zgd{PwLL7Y8iP#u8wYN_Jz;? z+8+H#*RwWBM#L`z9A-d&+d8z!jj7MrZ?zfGFgoZI@e@Aw7u-i@2koPN+}i6nq|Clb z;*jleq%PHu*cb6~b>#NI76hMc9GP6$EKuIu1o|HiLhg41DgK+UR-+3wOx$397zOTy zBX5~H#CH5r`$Uyd9feY}8|kQdXg~a(k}Q7HZE6|w%`U-2x z&rsTg-nV-}K4&|AMRmk}=uSBuwHewXL(@(bE|vx%&3A_=ew%3`Fqtu#_zU~Y)^8o! z(4?Fb`0>1!_625MOP1@3w#|wCvhS(s%@SCAsuCvO3PS!58t~h#T7_=*cuxH>XuStC ze>sHsgYEp|{6BJex?KLpq@R-Gf2M1#WJIP7w0IwcCIzcfyysrkLUQ$ff*12|v1|Y; zG%O~cvmHO4!~CDISEKw-sZt^K%km$$q>XUu@nfL6*&oeZSS7(&@iyIT0(#7QNd2*7 zaU684`9b`F@pG+?;;~iPxoL9zTiIsI)zMqJQ}`I8te-YmxCnnGJ%xNt1MA00hC~-9 zMH+#ziK?|VDFU%(2XL}rB9er@L6x-S; zj(oJ_o+W zXPlT(AzU5kfyN|zQ2gVTxq#d>UxK;D+uY$Q=!mZq!HbV~ZZ<;mO$&)X*p6S(codTK2p(DfvqOJLLhJ*;-@zSO zB&1UOHF43f;^B9Sznh~K>U94m#m}`mD$`pus*jeH>2h`S)APl0Fz1g5wp;JQv&ZNKqYK z%4!aq2X9+MCS^See{gRyAbzA7Td>hhUvz_vND`Fo^+&Glt*Euu@4eNM|6Zo-?{;iHC zjQA}V|Au3h2#&8R5e9AEh61X$Qv7ZG2BVB63|{OrrF?hD6bOkw*v>zm*I!a@EjO

%L>;HJP0jW#_`Q{=$qd=;!W+KH|Tnj()XDmaC&3 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

fiI~Udi$1nA}8CJ5(TWp4lCltcs(& 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 qu)|mf(v%0q2I3bXvY^PwzzsnWL2?Hn` \ No newline at end of file diff --git a/src/ck-ldb/XGBoost/model_tree/model.bin b/src/ck-ldb/XGBoost/model_tree/model.bin new file mode 100644 index 0000000000000000000000000000000000000000..510e5090a9b04cf2f7d501d2708d1ddf995100ce GIT binary patch literal 75410 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 \ No newline at end of file diff --git a/src/scripts/Makefile b/src/scripts/Makefile index 7269c3d873..df4be10de8 100644 --- a/src/scripts/Makefile +++ b/src/scripts/Makefile @@ -681,7 +681,7 @@ $(L)/libglobal-swap.a: global-elfgot.C $(CVHEADERS) LIBCK_CORE=trace-common.o tracec.o tracef.o init.o register.o qd.o ck.o \ msgalloc.o ckfutures.o ckIgetControl.o debug-message.o debug-charm.o ckcallback.o \ cklocation.o ckmulticast.o ckarrayoptions.o ckarray.o ckreduction.o ckrdma.o ckrdmadevice.o cksyncbarrier.o \ - waitqd.o LBDatabase.o LBManager.o MetaBalancer.o weakTest.o treeTest.o forestTest.o readmodel.o lbdbf.o ckobjQ.o \ + waitqd.o LBDatabase.o LBManager.o MetaBalancer.o weakTest.o treeTest.o forestTest.o readmodel.o lbdbf.o ckobjQ.o\ ckcheckpoint.o ckmemcheckpoint.o \ LBComm.o LBObj.o LBMachineUtil.o CentralPredictor.o \ BaseLB.o CentralLB.o TreeLB.o HybridBaseLB.o DistBaseLB.o \ diff --git a/src/util/fastforest.C b/src/util/fastforest.C new file mode 100644 index 0000000000..db9756b43b --- /dev/null +++ b/src/util/fastforest.C @@ -0,0 +1,397 @@ +/** +MIT License +Copyright (c) 2020 Jonas Rembser +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "fastforest.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace fastforest; + + +void fastforest::detail::correctIndices(std::vector::iterator begin, + std::vector::iterator end, + fastforest::detail::IndexMap const& nodeIndices, + fastforest::detail::IndexMap const& leafIndices) { + for (auto it = begin; it != end; ++it) { + if (nodeIndices.count(*it)) { + *it = nodeIndices.at(*it); + } else if (leafIndices.count(*it)) { + *it = -leafIndices.at(*it); + } else { + throw std::runtime_error("something is wrong in the node structure"); + } + } +} + +namespace { + namespace util { + + inline bool isInteger(const std::string& s) { + if (s.empty() || ((!isdigit(s[0])) && (s[0] != '-') && (s[0] != '+'))) + return false; + + char* p; + strtol(s.c_str(), &p, 10); + + return (*p == 0); + } + + template + struct NumericAfterSubstrOutput { + explicit NumericAfterSubstrOutput() : value{0}, found{false}, failed{true} {} + NumericType value; + bool found; + bool failed; + std::string rest; + }; + + template + inline NumericAfterSubstrOutput numericAfterSubstr(std::string const& str, + std::string const& substr) { + std::string rest; + NumericAfterSubstrOutput output; + output.rest = str; + + auto found = str.find(substr); + if (found != std::string::npos) { + output.found = true; + std::stringstream ss(str.substr(found + substr.size(), str.size() - found + substr.size())); + ss >> output.value; + if (!ss.fail()) { + output.failed = false; + output.rest = ss.str(); + } + } + return output; + } + + std::vector split(std::string const& strToSplit, char delimeter) { + std::stringstream ss(strToSplit); + std::string item; + std::vector splittedStrings; + while (std::getline(ss, item, delimeter)) { + splittedStrings.push_back(item); + } + return splittedStrings; + } + + bool exists(std::string const& filename) { + if (FILE* file = fopen(filename.c_str(), "r")) { + fclose(file); + return true; + } else { + return false; + } + } + + } // namespace util + + void terminateTree(fastforest::FastForest& ff, + int& nPreviousNodes, + int& nPreviousLeaves, + fastforest::detail::IndexMap& nodeIndices, + fastforest::detail::IndexMap& leafIndices, + int& treesSkipped) { + using namespace fastforest::detail; + correctIndices(ff.rightIndices_.begin() + nPreviousNodes, ff.rightIndices_.end(), nodeIndices, leafIndices); + correctIndices(ff.leftIndices_.begin() + nPreviousNodes, ff.leftIndices_.end(), nodeIndices, leafIndices); + + if (nPreviousNodes != ff.cutValues_.size()) { + ff.treeNumbers_.push_back(ff.rootIndices_.size() + treesSkipped); + ff.rootIndices_.push_back(nPreviousNodes); + } else { + int treeNumbers = ff.rootIndices_.size() + treesSkipped; + ++treesSkipped; + ff.baseResponses_[treeNumbers % ff.baseResponses_.size()] += ff.responses_.back(); + ff.responses_.pop_back(); + } + + nodeIndices.clear(); + leafIndices.clear(); + nPreviousNodes = ff.cutValues_.size(); + nPreviousLeaves = ff.responses_.size(); + } + +} // namespace + + +void fastforest::details::softmaxTransformInplace(TreeEnsembleResponseType* out, int nOut) { + // Do softmax transformation inplace, mimicing exactly the Softmax function + // in the src/common/math.h source file of xgboost. + double norm = 0.; + TreeEnsembleResponseType wmax = *out; + int i = 1; + for (; i < nOut; ++i) { + wmax = std::max(out[i], wmax); + } + i = 0; + for (; i < nOut; ++i) { + auto& x = out[i]; + x = std::exp(x - wmax); + norm += x; + } + i = 0; + for (; i < nOut; ++i) { + out[i] /= static_cast(norm); + } +} + +std::vector fastforest::FastForest::softmax(const FeatureType* array, + TreeEnsembleResponseType baseResponse) const { + auto out = std::vector(nClasses()); + softmax(array, out.data(), baseResponse); + return out; +} + +void fastforest::FastForest::softmax(const FeatureType* array, + TreeEnsembleResponseType* out, + TreeEnsembleResponseType baseResponse) const { + int nClass = nClasses(); + if (nClass <= 2) { + throw std::runtime_error( + "Error in FastForest::softmax : binary classification models don't support softmax evaluation. Plase set " + "the number of classes in the FastForest-creating function if this is a multiclassification model."); + } + + evaluate(array, out, nClass, baseResponse); + fastforest::details::softmaxTransformInplace(out, nClass); +} + +void fastforest::FastForest::evaluate(const FeatureType* array, + TreeEnsembleResponseType* out, + int nOut, + TreeEnsembleResponseType baseResponse) const { + for (int i = 0; i < nOut; ++i) { + out[i] = baseResponse + baseResponses_[i]; + } + + int iRootIndex = 0; + for (int index : rootIndices_) { + do { + auto r = rightIndices_[index]; + auto l = leftIndices_[index]; + index = array[cutIndices_[index]] > cutValues_[index] ? r : l; + } while (index > 0); + out[treeNumbers_[iRootIndex] % nOut] += responses_[-index]; + ++iRootIndex; + } +} + +TreeEnsembleResponseType fastforest::FastForest::evaluateBinary(const FeatureType* array, + TreeEnsembleResponseType baseResponse) const { + TreeEnsembleResponseType out{baseResponse + baseResponses_[0]}; + + for (int index : rootIndices_) { + do { + auto r = rightIndices_[index]; + auto l = leftIndices_[index]; + index = array[cutIndices_[index]] > cutValues_[index] ? r : l; + } while (index > 0); + out += responses_[-index]; + } + + return out; +} + +FastForest fastforest::load_bin(std::string const& txtpath) { + std::ifstream ifs(txtpath, std::ios::binary); + return load_bin(ifs); +} + +FastForest fastforest::load_bin(std::istream& is) { + FastForest ff; + + int nRootNodes; + int nNodes; + int nLeaves; + + is.read((char*)&nRootNodes, sizeof(int)); + is.read((char*)&nNodes, sizeof(int)); + is.read((char*)&nLeaves, sizeof(int)); + + ff.rootIndices_.resize(nRootNodes); + ff.cutIndices_.resize(nNodes); + ff.cutValues_.resize(nNodes); + ff.leftIndices_.resize(nNodes); + ff.rightIndices_.resize(nNodes); + ff.responses_.resize(nLeaves); + ff.treeNumbers_.resize(nRootNodes); + + is.read((char*)ff.rootIndices_.data(), nRootNodes * sizeof(int)); + is.read((char*)ff.cutIndices_.data(), nNodes * sizeof(CutIndexType)); + is.read((char*)ff.cutValues_.data(), nNodes * sizeof(FeatureType)); + is.read((char*)ff.leftIndices_.data(), nNodes * sizeof(int)); + is.read((char*)ff.rightIndices_.data(), nNodes * sizeof(int)); + is.read((char*)ff.responses_.data(), nLeaves * sizeof(TreeResponseType)); + is.read((char*)ff.treeNumbers_.data(), nRootNodes * sizeof(int)); + + int nBaseResponses; + is.read((char*)&nBaseResponses, sizeof(int)); + ff.baseResponses_.resize(nBaseResponses); + is.read((char*)ff.baseResponses_.data(), nBaseResponses * sizeof(TreeEnsembleResponseType)); + + return ff; +} + +void fastforest::FastForest::write_bin(std::string const& filename) const { + std::ofstream os(filename, std::ios::binary); + + int nRootNodes = rootIndices_.size(); + int nNodes = cutValues_.size(); + int nLeaves = responses_.size(); + int nBaseResponses = baseResponses_.size(); + + os.write((const char*)&nRootNodes, sizeof(int)); + os.write((const char*)&nNodes, sizeof(int)); + os.write((const char*)&nLeaves, sizeof(int)); + + os.write((const char*)rootIndices_.data(), nRootNodes * sizeof(int)); + os.write((const char*)cutIndices_.data(), nNodes * sizeof(CutIndexType)); + os.write((const char*)cutValues_.data(), nNodes * sizeof(FeatureType)); + os.write((const char*)leftIndices_.data(), nNodes * sizeof(int)); + os.write((const char*)rightIndices_.data(), nNodes * sizeof(int)); + os.write((const char*)responses_.data(), nLeaves * sizeof(TreeResponseType)); + os.write((const char*)treeNumbers_.data(), nRootNodes * sizeof(int)); + + os.write((const char*)&nBaseResponses, sizeof(int)); + os.write((const char*)baseResponses_.data(), nBaseResponses * sizeof(TreeEnsembleResponseType)); + os.close(); +} + +FastForest fastforest::load_txt(std::string const& txtpath, std::vector& features, int nClasses) { + const std::string info = "constructing FastForest from " + txtpath + ": "; + + if (!util::exists(txtpath)) { + throw std::runtime_error(info + "file does not exists"); + } + + std::ifstream file(txtpath); + return load_txt(file, features, nClasses); +} + +FastForest fastforest::load_txt(std::istream& file, std::vector& features, int nClasses) { + if (nClasses < 2) { + throw std::runtime_error("Error in fastforest::load_txt : nClasses has to be at least two"); + } + + const std::string info = "constructing FastForest from istream: "; + + FastForest ff; + ff.baseResponses_.resize(nClasses == 2 ? 1 : nClasses); + + int treesSkipped = 0; + + int nVariables = 0; + std::unordered_map varIndices; + bool fixFeatures = false; + + if (!features.empty()) { + fixFeatures = true; + nVariables = features.size(); + for (int i = 0; i < nVariables; ++i) { + varIndices[features[i]] = i; + } + } + + std::string line; + + fastforest::detail::IndexMap nodeIndices; + fastforest::detail::IndexMap leafIndices; + + int nPreviousNodes = 0; + int nPreviousLeaves = 0; + + while (std::getline(file, line)) { + auto foundBegin = line.find("["); + auto foundEnd = line.find("]"); + if (foundBegin != std::string::npos) { + auto subline = line.substr(foundBegin + 1, foundEnd - foundBegin - 1); + if (util::isInteger(subline) && !ff.responses_.empty()) { + terminateTree(ff, nPreviousNodes, nPreviousLeaves, nodeIndices, leafIndices, treesSkipped); + } else if (!util::isInteger(subline)) { + std::stringstream ss(line); + int index; + ss >> index; + line = ss.str(); + + auto splitstring = util::split(subline, '<'); + auto const& varName = splitstring[0]; + FeatureType cutValue = std::stold(splitstring[1]); + if (!varIndices.count(varName)) { + if (fixFeatures) { + throw std::runtime_error(info + "feature " + varName + " not in list of features"); + } + varIndices[varName] = nVariables; + features.push_back(varName); + ++nVariables; + } + int yes; + int no; + auto output = util::numericAfterSubstr(line, "yes="); + if (!output.failed) { + yes = output.value; + } else { + throw std::runtime_error(info + "problem while parsing the text dump"); + } + output = util::numericAfterSubstr(output.rest, "no="); + if (!output.failed) { + no = output.value; + } else { + throw std::runtime_error(info + "problem while parsing the text dump"); + } + + ff.cutValues_.push_back(cutValue); + ff.cutIndices_.push_back(varIndices[varName]); + ff.leftIndices_.push_back(yes); + ff.rightIndices_.push_back(no); + auto nNodeIndices = nodeIndices.size(); + nodeIndices[index] = nNodeIndices + nPreviousNodes; + } + + } else { + auto output = util::numericAfterSubstr(line, "leaf="); + if (output.found) { + std::stringstream ss(line); + int index; + ss >> index; + line = ss.str(); + + ff.responses_.push_back(output.value); + auto nLeafIndices = leafIndices.size(); + leafIndices[index] = nLeafIndices + nPreviousLeaves; + } + } + } + terminateTree(ff, nPreviousNodes, nPreviousLeaves, nodeIndices, leafIndices, treesSkipped); + + if (nClasses > 2 && (ff.rootIndices_.size() + treesSkipped) % nClasses != 0) { + throw std::runtime_error(std::string{"Error in FastForest construction : Forest has "} + + std::to_string(ff.rootIndices_.size()) + " trees, " + "which is not compatible with " + + std::to_string(nClasses) + " classes!"); + } + + return ff; +} \ No newline at end of file diff --git a/src/util/fastforest.h b/src/util/fastforest.h new file mode 100644 index 0000000000..92b49421a3 --- /dev/null +++ b/src/util/fastforest.h @@ -0,0 +1,123 @@ +/** +MIT License +Copyright (c) 2020 Jonas Rembser +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef FastForest_h +#define FastForest_h + +#include +#include +#include +#include +#include +#include + +namespace fastforest { + + // The double number type that will be used to accept features and store cut values + typedef double FeatureType; + // Tue double number type that the individual trees return their responses in + typedef double TreeResponseType; + // The double number type that is used to sum the individual tree responses + typedef double TreeEnsembleResponseType; + // This integer type stores the indices of the feature employed in each cut. + // Set to `unsigned char` for most compact fastforest ofjects if you have less than 256 features. + typedef unsigned int CutIndexType; + + namespace detail { + + typedef std::unordered_map IndexMap; + + void correctIndices(std::vector::iterator begin, + std::vector::iterator end, + IndexMap const& nodeIndices, + IndexMap const& leafIndices); + + } + + // The base response you have to use with older XGBoost versions might be + // zero, so try to explicitely pass zero to the model evaluation if the + // results from this library are incorrect. + const TreeEnsembleResponseType defaultBaseResponse = 0.5; + + namespace details { + + void softmaxTransformInplace(TreeEnsembleResponseType* out, int nOut); + + } + + struct FastForest { + inline TreeEnsembleResponseType operator()(const FeatureType* array, + TreeEnsembleResponseType baseResponse = defaultBaseResponse) const { + return evaluateBinary(array, baseResponse); + } + + template + std::array softmax( + const FeatureType* array, TreeEnsembleResponseType baseResponse = defaultBaseResponse) const { + // static softmax interface: no manual memory allocation, but requires to know nClasses at compile time + static_assert(nClasses >= 3, "nClasses should be >= 3"); + std::array out{}; + evaluate(array, out.data(), nClasses, baseResponse); + details::softmaxTransformInplace(out.data(), nClasses); + return out; + } + + // dynamic softmax interface with manually allocated std::vector: simple but inefficient + std::vector softmax( + const FeatureType* array, TreeEnsembleResponseType baseResponse = defaultBaseResponse) const; + + // softmax interface that is not a pure function, but no manual allocation and no compile-time knowledge needed + void softmax(const FeatureType* array, + TreeEnsembleResponseType* out, + TreeEnsembleResponseType baseResponse = defaultBaseResponse) const; + + void write_bin(std::string const& filename) const; + + int nClasses() const { return baseResponses_.size() > 2 ? baseResponses_.size() : 2; } + + std::vector rootIndices_; + std::vector cutIndices_; + std::vector cutValues_; + std::vector leftIndices_; + std::vector rightIndices_; + std::vector responses_; + std::vector treeNumbers_; + std::vector baseResponses_; + + private: + void evaluate(const FeatureType* array, + TreeEnsembleResponseType* out, + int nOut, + TreeEnsembleResponseType baseResponse) const; + + TreeEnsembleResponseType evaluateBinary(const FeatureType* array, TreeEnsembleResponseType baseResponse) const; + }; + + FastForest load_txt(std::string const& txtpath, std::vector& features, int nClasses = 2); + FastForest load_txt(std::istream& is, std::vector& features, int nClasses = 2); + FastForest load_bin(std::string const& txtpath); + FastForest load_bin(std::istream& is); +#ifdef EXPERIMENTAL_TMVA_SUPPORT + FastForest load_tmva_xml(std::string const& xmlpath, std::vector& features); +#endif + +} // namespace fastforest + +#endif \ No newline at end of file