From a09770d34be1a6a0ca296241bf8f5e44d4b4e395 Mon Sep 17 00:00:00 2001 From: Huamin Chen Date: Mon, 16 Oct 2023 14:57:48 -0400 Subject: [PATCH] regressor: support local xgboost regressor Signed-off-by: Huamin Chen --- pkg/config/config.go | 14 +- pkg/model/estimator/local/lr.go | 126 +++++++++++----- pkg/model/estimator/local/lr_test.go | 25 +-- .../estimator/local/xgboost_model_weight.go | 142 ++++++++++++++++++ pkg/model/model.go | 20 +-- pkg/model/types/types.go | 22 ++- 6 files changed, 284 insertions(+), 65 deletions(-) create mode 100644 pkg/model/estimator/local/xgboost_model_weight.go diff --git a/pkg/config/config.go b/pkg/config/config.go index 0820c63aa3..7a4289608b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -121,13 +121,13 @@ var ( ProcessComponentsPowerKey = "PROCESS_COMPONENTS" // model_parameter_attribute - RatioEnabledKey = "RATIO" // the default container power model is RATIO but ESTIMATOR or LINEAR_REGRESSION can be used - EstimatorEnabledKey = "ESTIMATOR" - LinearRegressionEnabledKey = "LINEAR_REGRESSION" - InitModelURLKey = "INIT_URL" - FixedTrainerNameKey = "TRAINER" - FixedNodeTypeKey = "NODE_TYPE" - ModelFiltersKey = "FILTERS" + RatioEnabledKey = "RATIO" // the default container power model is RATIO but ESTIMATOR or LINEAR_REGRESSION can be used + EstimatorEnabledKey = "ESTIMATOR" + LocalRegressorEnabledKey = "LOCAL_REGRESSION" + InitModelURLKey = "INIT_URL" + FixedTrainerNameKey = "TRAINER" + FixedNodeTypeKey = "NODE_TYPE" + ModelFiltersKey = "FILTERS" //////////////////////////////////// // KubeConfig is used to start k8s client with the pod running outside the cluster diff --git a/pkg/model/estimator/local/lr.go b/pkg/model/estimator/local/lr.go index 6eccddeef3..af95e0ce54 100644 --- a/pkg/model/estimator/local/lr.go +++ b/pkg/model/estimator/local/lr.go @@ -59,26 +59,40 @@ type ModelRequest struct { /* ModelWeights, AllWeight, CategoricalFeature, NormalizedNumericalFeature define structure of model weight { -"All_Weights": - + "All_Weights": { "Bias_Weight": 1.0, "Categorical_Variables": {"cpu_architecture": {"Sky Lake": {"weight": 1.0}}}, "Numerical_Variables": {"cpu_cycles": {"mean": 0, "variance": 1.0, "weight": 1.0}} } + }, + "XGboost_Weights": { + "learner": { + "gradient_booster": { + "model": { + "trees": [] + } + } } +} */ + type ModelWeights struct { - AllWeights `json:"All_Weights"` + AllWeights `json:"All_Weights"` + XGBoostModelWeight `json:"XGBoost_Weights"` + RegressorType types.RegressorType } + type AllWeights struct { BiasWeight float64 `json:"Bias_Weight"` CategoricalVariables map[string]map[string]CategoricalFeature `json:"Categorical_Variables"` NumericalVariables map[string]NormalizedNumericalFeature `json:"Numerical_Variables"` } + type CategoricalFeature struct { Weight float64 `json:"weight"` } + type NormalizedNumericalFeature struct { Scale float64 `json:"scale"` // to normalize the data Weight float64 `json:"weight"` @@ -96,7 +110,7 @@ func (weights ModelWeights) getIndexedWeights(usageMetrics, systemFeatures []str return } -// predict applies normalization and linear regression to usageMetricValues and systemMetaDataFeatureValues +// predict applies normalization and local regression to usageMetricValues and systemMetaDataFeatureValues func (weights ModelWeights) predict(usageMetricNames []string, usageMetricValues [][]float64, systemMetaDataFeatureNames, systemMetaDataFeatureValues []string) []float64 { categoricalWeights, numericalWeights := weights.getIndexedWeights(usageMetricNames, systemMetaDataFeatureNames) basePower := weights.AllWeights.BiasWeight @@ -141,10 +155,35 @@ ComponentModelWeights defines structure for multiple (power component's) weights } } */ + +/* + the xgboost model is like this: + { + "package": { + "All_Weights": { + "Bias_Weight": 0, + "Categorical_Variables": { + + }, + "Numerical_Variables": { + "bpf_cpu_time_us": { + "scale": 29170.333333333332, + "mean": 0, + "variance": 0, + "weight": { + "learner": { + gradient_booster": { + } + } + } + } + } +*/ + type ComponentModelWeights map[string]ModelWeights -// LinearRegressor defines power estimator with linear regression approach -type LinearRegressor struct { +// LocalRegressor defines power estimator with regression approach +type LocalRegressor struct { ModelServerEndpoint string OutputType types.ModelOutputType EnergySource string @@ -168,8 +207,26 @@ type LinearRegressor struct { modelWeight *ComponentModelWeights } +// init model weight +func (r *LocalRegressor) initModelWeight(content *ComponentModelWeights) error { + r.modelWeight = content + weight := *r.modelWeight + for k, v := range weight { + if v.XGBoostModelWeight.Learner != "" { + v.RegressorType = types.XGBoostRegressor + err := v.XGBoostModelWeight.LoadFromBuffer() + if err != nil { + return fmt.Errorf("failed to load %v xgboost model: %v", k, err) + } + } else { + v.RegressorType = types.LinearRegressor + } + } + return nil +} + // Start returns nil if model weight is obtainable -func (r *LinearRegressor) Start() error { +func (r *LocalRegressor) Start() error { var err error var weight *ComponentModelWeights outputStr := r.OutputType.String() @@ -186,8 +243,7 @@ func (r *LinearRegressor) Start() error { } if weight != nil { r.enabled = true - r.modelWeight = weight - return nil + return r.initModelWeight(weight) } else { if err == nil { err = fmt.Errorf("the model LR (%s): has no config", outputStr) @@ -198,7 +254,7 @@ func (r *LinearRegressor) Start() error { } // getWeightFromServer tries getting weights for Kepler Model Server -func (r *LinearRegressor) getWeightFromServer() (*ComponentModelWeights, error) { +func (r *LocalRegressor) getWeightFromServer() (*ComponentModelWeights, error) { modelRequest := ModelRequest{ MetricNames: append(r.FloatFeatureNames, r.SystemMetaDataFeatureNames...), OutputType: r.OutputType.String(), @@ -242,7 +298,7 @@ func (r *LinearRegressor) getWeightFromServer() (*ComponentModelWeights, error) // loadWeightFromURLorLocal get weight from either local or URL // if string start with '/', we take it as local file -func (r *LinearRegressor) loadWeightFromURLorLocal() (*ComponentModelWeights, error) { +func (r *LocalRegressor) loadWeightFromURLorLocal() (*ComponentModelWeights, error) { var body []byte var err error @@ -262,7 +318,7 @@ func (r *LinearRegressor) loadWeightFromURLorLocal() (*ComponentModelWeights, er } // loadWeightFromLocal tries loading weights from local file given by r.ModelWeightsURL -func (r *LinearRegressor) loadWeightFromLocal() ([]byte, error) { +func (r *LocalRegressor) loadWeightFromLocal() ([]byte, error) { data, err := os.ReadFile(r.ModelWeightsFilepath) if err != nil { return nil, err @@ -271,7 +327,7 @@ func (r *LinearRegressor) loadWeightFromLocal() ([]byte, error) { } // loadWeightFromURL tries loading weights from initial model URL -func (r *LinearRegressor) loadWeightFromURL() ([]byte, error) { +func (r *LocalRegressor) loadWeightFromURL() ([]byte, error) { if r.ModelWeightsURL == "" { return nil, fmt.Errorf("ModelWeightsURL is empty") } @@ -295,7 +351,7 @@ func (r *LinearRegressor) loadWeightFromURL() ([]byte, error) { } // GetPlatformPower applies ModelWeight prediction and return a list of power associated to each process/container/pod -func (r *LinearRegressor) GetPlatformPower(isIdlePower bool) ([]float64, error) { +func (r *LocalRegressor) GetPlatformPower(isIdlePower bool) ([]float64, error) { if !r.enabled { return []float64{}, fmt.Errorf("disabled power model call: %s", r.OutputType.String()) } @@ -316,7 +372,7 @@ func (r *LinearRegressor) GetPlatformPower(isIdlePower bool) ([]float64, error) } // GetComponentsPower applies each component's ModelWeight prediction and return a map of component power associated to each process/container/pod -func (r *LinearRegressor) GetComponentsPower(isIdlePower bool) ([]source.NodeComponentsEnergy, error) { +func (r *LocalRegressor) GetComponentsPower(isIdlePower bool) ([]source.NodeComponentsEnergy, error) { if !r.enabled { return []source.NodeComponentsEnergy{}, fmt.Errorf("disabled power model call: %s", r.OutputType.String()) } @@ -349,11 +405,11 @@ func (r *LinearRegressor) GetComponentsPower(isIdlePower bool) ([]source.NodeCom } // GetComponentsPower returns GPU Power in Watts associated to each each process/container/pod -func (r *LinearRegressor) GetGPUPower(isIdlePower bool) ([]float64, error) { +func (r *LocalRegressor) GetGPUPower(isIdlePower bool) ([]float64, error) { return []float64{}, fmt.Errorf("current power model does not support GPUs") } -func (r *LinearRegressor) addFloatFeatureValues(x []float64) { +func (r *LocalRegressor) addFloatFeatureValues(x []float64) { for i, feature := range x { // floatFeatureValues is a cyclic list, where we only append a new value if it is necessary. if r.xidx < len(r.floatFeatureValues) { @@ -377,53 +433,53 @@ func (r *LinearRegressor) addFloatFeatureValues(x []float64) { } // AddContainerFeatureValues adds the the x for prediction, which are the explanatory variables (or the independent variable) of regression. -// LinearRegressor is trained off-line then we cannot Add training samples. We might implement it in the future. -// The LinearRegressor does not differentiate node or container power estimation, the difference will only be the amount of resource utilization -func (r *LinearRegressor) AddContainerFeatureValues(x []float64) { +// LocalRegressor is trained off-line then we cannot Add training samples. We might implement it in the future. +// The LocalRegressor does not differentiate node or container power estimation, the difference will only be the amount of resource utilization +func (r *LocalRegressor) AddContainerFeatureValues(x []float64) { r.addFloatFeatureValues(x) } // AddNodeFeatureValues adds the the x for prediction, which is the variable used to calculate the ratio. -// LinearRegressor is not trained, then we cannot Add training samples, only samples for prediction. -// The LinearRegressor does not differentiate node or container power estimation, the difference will only be the amount of resource utilization -func (r *LinearRegressor) AddNodeFeatureValues(x []float64) { +// LocalRegressor is not trained, then we cannot Add training samples, only samples for prediction. +// The LocalRegressor does not differentiate node or container power estimation, the difference will only be the amount of resource utilization +func (r *LocalRegressor) AddNodeFeatureValues(x []float64) { r.addFloatFeatureValues(x) } // AddDesiredOutValue adds the the y, which is the response variable (or the dependent variable) of regression. -// LinearRegressor is trained off-line then we do not add Y for trainning. We might implement it in the future. -func (r *LinearRegressor) AddDesiredOutValue(y float64) { +// LocalRegressor is trained off-line then we do not add Y for trainning. We might implement it in the future. +func (r *LocalRegressor) AddDesiredOutValue(y float64) { } // ResetSampleIdx set the sample vector index to 0 to overwrite the old samples with new ones for trainning or prediction. -func (r *LinearRegressor) ResetSampleIdx() { +func (r *LocalRegressor) ResetSampleIdx() { r.xidx = 0 } // Train triggers the regressiong fit after adding data points to create a new power model. -// LinearRegressor is trained off-line then we cannot trigger the trainning. We might implement it in the future. -func (r *LinearRegressor) Train() error { +// LocalRegressor is trained off-line then we cannot trigger the trainning. We might implement it in the future. +func (r *LocalRegressor) Train() error { return nil } // IsEnabled returns true if the power model was trained and is active -func (r *LinearRegressor) IsEnabled() bool { +func (r *LocalRegressor) IsEnabled() bool { return r.enabled } // GetModelType returns the model type -func (r *LinearRegressor) GetModelType() types.ModelType { - return types.LinearRegressor +func (r *LocalRegressor) GetModelType() types.ModelType { + return types.LocalRegressor } // GetContainerFeatureNamesList returns the list of float features that the model was configured to use -// The LinearRegressor does not differentiate node or container power estimation, the difference will only be the amount of resource utilization -func (r *LinearRegressor) GetContainerFeatureNamesList() []string { +// The LocalRegressor does not differentiate node or container power estimation, the difference will only be the amount of resource utilization +func (r *LocalRegressor) GetContainerFeatureNamesList() []string { return r.FloatFeatureNames } // GetNodeFeatureNamesList returns the list of float features that the model was configured to use -// The LinearRegressor does not differentiate node or container power estimation, the difference will only be the amount of resource utilization -func (r *LinearRegressor) GetNodeFeatureNamesList() []string { +// The LocalRegressor does not differentiate node or container power estimation, the difference will only be the amount of resource utilization +func (r *LocalRegressor) GetNodeFeatureNamesList() []string { return r.FloatFeatureNames } diff --git a/pkg/model/estimator/local/lr_test.go b/pkg/model/estimator/local/lr_test.go index 230543175c..f759d8bab9 100644 --- a/pkg/model/estimator/local/lr_test.go +++ b/pkg/model/estimator/local/lr_test.go @@ -16,7 +16,8 @@ limitations under the License. /* lr.go -estimate (node/pod) component and total power by linear regression approach when trained model weights are available. +estimate (node/pod) component and total power by local regression approach when trained model weights are available. +The regressor can be linear or non-linear. The model weights can be obtained by Kepler Model Server or configured initial model URL. */ @@ -92,6 +93,8 @@ func genWeights(numericalVars map[string]NormalizedNumericalFeature) ModelWeight CategoricalVariables: map[string]map[string]CategoricalFeature{"cpu_architecture": SampleCategoricalFeatures}, NumericalVariables: numericalVars, }, + XGBoostModelWeight{}, + types.RegressorType(1), } } @@ -115,10 +118,10 @@ func getDummyWeights(w http.ResponseWriter, r *http.Request) { } } -func genLinearRegressor(outputType types.ModelOutputType, energySource, modelServerEndpoint, modelWeightsURL, modelWeightFilepath string) LinearRegressor { +func genLocalRegressor(outputType types.ModelOutputType, energySource, modelServerEndpoint, modelWeightsURL, modelWeightFilepath string) LocalRegressor { config.ModelServerEnable = true config.ModelServerEndpoint = modelServerEndpoint - return LinearRegressor{ + return LocalRegressor{ ModelServerEndpoint: modelServerEndpoint, OutputType: outputType, EnergySource: energySource, @@ -135,7 +138,7 @@ var _ = Describe("Test LR Weight Unit", func() { It("Get Node Platform Power By Linear Regression with ModelServerEndpoint", func() { testServer := httptest.NewServer(http.HandlerFunc(getDummyWeights)) modelWeightFilepath := config.GetDefaultPowerModelURL(types.AbsPower.String(), types.PlatformEnergySource) - r := genLinearRegressor(types.AbsPower, types.PlatformEnergySource, testServer.URL, "", modelWeightFilepath) + r := genLocalRegressor(types.AbsPower, types.PlatformEnergySource, testServer.URL, "", modelWeightFilepath) err := r.Start() Expect(err).To(BeNil()) r.ResetSampleIdx() @@ -150,7 +153,7 @@ var _ = Describe("Test LR Weight Unit", func() { It("Get Node Components Power By Linear Regression Estimator with ModelServerEndpoint", func() { testServer := httptest.NewServer(http.HandlerFunc(getDummyWeights)) modelWeightFilepath := config.GetDefaultPowerModelURL(types.AbsPower.String(), types.ComponentEnergySource) - r := genLinearRegressor(types.AbsPower, types.ComponentEnergySource, testServer.URL, "", modelWeightFilepath) + r := genLocalRegressor(types.AbsPower, types.ComponentEnergySource, testServer.URL, "", modelWeightFilepath) err := r.Start() Expect(err).To(BeNil()) r.ResetSampleIdx() @@ -165,7 +168,7 @@ var _ = Describe("Test LR Weight Unit", func() { It("Get Container Platform Power By Linear Regression Estimator with ModelServerEndpoint", func() { testServer := httptest.NewServer(http.HandlerFunc(getDummyWeights)) modelWeightFilepath := config.GetDefaultPowerModelURL(types.DynPower.String(), types.PlatformEnergySource) - r := genLinearRegressor(types.DynPower, types.PlatformEnergySource, testServer.URL, "", modelWeightFilepath) + r := genLocalRegressor(types.DynPower, types.PlatformEnergySource, testServer.URL, "", modelWeightFilepath) err := r.Start() Expect(err).To(BeNil()) r.ResetSampleIdx() @@ -182,7 +185,7 @@ var _ = Describe("Test LR Weight Unit", func() { It("Get Container Components Power By Linear Regression Estimator with ModelServerEndpoint", func() { testServer := httptest.NewServer(http.HandlerFunc(getDummyWeights)) modelWeightFilepath := config.GetDefaultPowerModelURL(types.DynPower.String(), types.ComponentEnergySource) - r := genLinearRegressor(types.DynPower, types.ComponentEnergySource, testServer.URL, "", modelWeightFilepath) + r := genLocalRegressor(types.DynPower, types.ComponentEnergySource, testServer.URL, "", modelWeightFilepath) err := r.Start() Expect(err).To(BeNil()) r.ResetSampleIdx() @@ -202,7 +205,7 @@ var _ = Describe("Test LR Weight Unit", func() { /// Estimate Node Components Power using Linear Regression modelWeightFilepath := config.GetDefaultPowerModelURL(types.AbsPower.String(), types.ComponentEnergySource) initModelURL := "https://raw.githubusercontent.com/sustainable-computing-io/kepler-model-db/main/models/v0.6/nx12/std_v0.6/acpi/AbsPower/BPFOnly/SGDRegressorTrainer_1.json" - r := genLinearRegressor(types.AbsPower, types.PlatformEnergySource, "", initModelURL, modelWeightFilepath) + r := genLocalRegressor(types.AbsPower, types.PlatformEnergySource, "", initModelURL, modelWeightFilepath) err := r.Start() Expect(err).To(BeNil()) r.ResetSampleIdx() @@ -215,7 +218,7 @@ var _ = Describe("Test LR Weight Unit", func() { /// Estimate Node Components Power using Linear Regression modelWeightFilepath := config.GetDefaultPowerModelURL(types.AbsPower.String(), types.ComponentEnergySource) initModelURL := "https://raw.githubusercontent.com/sustainable-computing-io/kepler-model-db/main/models/v0.6/nx12/std_v0.6/rapl/AbsPower/BPFOnly/SGDRegressorTrainer_1.json" - r := genLinearRegressor(types.AbsPower, types.ComponentEnergySource, "", initModelURL, modelWeightFilepath) + r := genLocalRegressor(types.AbsPower, types.ComponentEnergySource, "", initModelURL, modelWeightFilepath) err := r.Start() Expect(err).To(BeNil()) r.ResetSampleIdx() @@ -228,7 +231,7 @@ var _ = Describe("Test LR Weight Unit", func() { // Estimate Container Components Power using Linear Regression modelWeightFilepath := config.GetDefaultPowerModelURL(types.DynPower.String(), types.ComponentEnergySource) initModelURL := "https://raw.githubusercontent.com/sustainable-computing-io/kepler-model-db/main/models/v0.6/nx12/std_v0.6/acpi/DynPower/BPFOnly/SGDRegressorTrainer_1.json" - r := genLinearRegressor(types.DynPower, types.PlatformEnergySource, "", initModelURL, modelWeightFilepath) + r := genLocalRegressor(types.DynPower, types.PlatformEnergySource, "", initModelURL, modelWeightFilepath) err := r.Start() Expect(err).To(BeNil()) r.ResetSampleIdx() @@ -243,7 +246,7 @@ var _ = Describe("Test LR Weight Unit", func() { // Estimate Container Components Power using Linear Regression modelWeightFilepath := config.GetDefaultPowerModelURL(types.DynPower.String(), types.ComponentEnergySource) initModelURL := "https://raw.githubusercontent.com/sustainable-computing-io/kepler-model-db/main/models/v0.6/nx12/std_v0.6/rapl/DynPower/BPFOnly/SGDRegressorTrainer_1.json" - r := genLinearRegressor(types.DynPower, types.ComponentEnergySource, "", initModelURL, modelWeightFilepath) + r := genLocalRegressor(types.DynPower, types.ComponentEnergySource, "", initModelURL, modelWeightFilepath) err := r.Start() Expect(err).To(BeNil()) r.ResetSampleIdx() diff --git a/pkg/model/estimator/local/xgboost_model_weight.go b/pkg/model/estimator/local/xgboost_model_weight.go new file mode 100644 index 0000000000..54cee81e2a --- /dev/null +++ b/pkg/model/estimator/local/xgboost_model_weight.go @@ -0,0 +1,142 @@ +/* +Copyright 2023. + +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. +*/ + +package local + +/* +#cgo LDFLAGS: -lxgboost -L/usr/local/lib64 +#include +#include +#include + +// load model from json file +BoosterHandle LoadModelFromJSON(const char* filename) { + BoosterHandle h_booster; + XGBoosterCreate(NULL, 0, &h_booster); + XGBoosterLoadModel(h_booster, filename); + return h_booster; +} + +// load model from buffer +BoosterHandle LoadModelFromBuffer(const char* buffer, int len) { + BoosterHandle h_booster; + XGBoosterCreate(NULL, 0, &h_booster); + XGBoosterLoadModelFromBuffer(h_booster, buffer, len); + return h_booster; +} + +int GetNumFeatures(BoosterHandle h_booster) { + bst_ulong num_features; + XGBoosterGetNumFeature(h_booster, &num_features); + return num_features; +} + +int Predict(BoosterHandle h_booster, float *data, int rows, int cols, float *output) { + DMatrixHandle h_mat; + XGDMatrixCreateFromMat((float *)data, rows, cols, -1, &h_mat); + bst_ulong out_len; + const float *f; + XGBoosterPredict(h_booster, h_mat, 0, 0, 0, &out_len, &f); + if (out_len != rows) { + printf("error: output length is not equal to input rows\n"); + XGDMatrixFree(h_mat); + return 0; + } + for (int i = 0; i < out_len; i++) { + output[i] = f[i]; + } + XGDMatrixFree(h_mat); + return out_len; +} +*/ +import "C" +import ( + "fmt" + "unsafe" +) + +type XGBoostModelWeight struct { + hBooster C.BoosterHandle + num_features int + Learner string `json:"learner"` // xgboost model learner +} + +func (m *XGBoostModelWeight) LoadFromJson(modelPath string) error { + m.hBooster = C.LoadModelFromJSON(C.CString(modelPath)) + if m.hBooster == nil { + return fmt.Errorf("load model from json %s failed", modelPath) + } + m.num_features = int(C.GetNumFeatures(m.hBooster)) + return nil +} + +func (m *XGBoostModelWeight) LoadFromBuffer() error { + modelBuffer := []byte(m.Learner) + m.hBooster = C.LoadModelFromBuffer((*C.char)(unsafe.Pointer(&modelBuffer[0])), C.int(len(modelBuffer))) + if m.hBooster == nil { + return fmt.Errorf("load model from buffer failed") + } + m.num_features = int(C.GetNumFeatures(m.hBooster)) + return nil +} + +func (m *XGBoostModelWeight) Close() { + C.XGBoosterFree(m.hBooster) +} + +func (m *XGBoostModelWeight) PredictFromData(data []float32) ([]float64, error) { + if len(data)%m.num_features != 0 { + return nil, fmt.Errorf("data length (%d) is not mutiple of num of features (%d)", len(data), m.num_features) + } + rows := len(data) / m.num_features + + output := C.malloc(C.size_t(rows) * C.size_t(C.sizeof_float)) + out_len := int(C.Predict(m.hBooster, (*C.float)(&data[0]), /* input */ + C.int(rows) /* rows */, C.int(m.num_features) /* cols */, (*C.float)(output) /* predict outpout */)) + if out_len < 1 { + return nil, fmt.Errorf("predict failed") + } + output_array := make([]float64, out_len) + for i := 0; i < out_len; i++ { + output_array[i] = float64(*(*float32)(unsafe.Pointer(uintptr(output) + uintptr(i)*unsafe.Sizeof(float32(0))))) + } + C.free(unsafe.Pointer(output)) + return output_array, nil +} + +func (m XGBoostModelWeight) predict(usageMetricNames []string, usageMetricValues [][]float64, systemMetaDataFeatureNames, systemMetaDataFeatureValues []string) []float64 { + output := make([]float64, len(usageMetricValues)) + // FIXME: how to use systmeMetaDataFeatureNames and systemMetaDataFeatureValues? + for i, usageMetricValue := range usageMetricValues { + if m.num_features != len(usageMetricValue) { + panic("xgboost model features and usageMetricValues length not equal") + } + // TODO we should really make a float32 array since xgboost only support float32 + data := make([]float32, len(usageMetricValue)) + for i, v := range usageMetricValue { + data[i] = float32(v) + } + val, err := m.PredictFromData(data) + if err != nil { + panic(err) + } + if len(val) != 1 { + panic("xgboost model predict length not equal to 1") + } + output[i] = val[0] + } + return output +} diff --git a/pkg/model/model.go b/pkg/model/model.go index e7257ef6b4..8e447db002 100644 --- a/pkg/model/model.go +++ b/pkg/model/model.go @@ -54,7 +54,7 @@ type PowerMoldelInterface interface { Train() error // IsEnabled returns true if the power model was trained and is active IsEnabled() bool - // GetModelType returns if the model is Ratio, LinearRegressor or EstimatorSidecar + // GetModelType returns if the model is Ratio, LocalRegressor or EstimatorSidecar GetModelType() types.ModelType // GetContainerFeatureNamesList returns the list of container features that the model was configured to use GetContainerFeatureNamesList() []string @@ -82,7 +82,7 @@ func CreatePowerEstimatorModels(containerFeatureNames, systemMetaDataFeatureName } // createPowerModelEstimator called by CreatePowerEstimatorModels to initiate estimate function for each power model. -// To estimate the power using the trained models with the model server, we can choose between using the EstimatorSidecar or the LinearRegressor. +// To estimate the power using the trained models with the model server, we can choose between using the EstimatorSidecar or the LocalRegressor. // For the built-in Power Model, we have the option to use the Ratio power model. func createPowerModelEstimator(modelConfig *types.ModelConfig) (PowerMoldelInterface, error) { switch modelConfig.ModelType { @@ -94,14 +94,14 @@ func createPowerModelEstimator(modelConfig *types.ModelConfig) (PowerMoldelInter klog.V(3).Infof("Using Power Model Ratio") return model, nil - case types.LinearRegressor: + case types.LocalRegressor: var featuresNames []string if modelConfig.IsNodePowerModel { featuresNames = modelConfig.NodeFeatureNames } else { featuresNames = modelConfig.ContainerFeatureNames } - model := &local.LinearRegressor{ + model := &local.LocalRegressor{ ModelServerEndpoint: config.ModelServerEndpoint, OutputType: modelConfig.ModelOutputType, EnergySource: modelConfig.EnergySource, @@ -190,14 +190,14 @@ func getPowerModelType(powerSourceTarget string) (modelType types.ModelType) { modelType = types.EstimatorSidecar return } - useLinearRegressionStr := config.ModelConfigValues[getModelConfigKey(powerSourceTarget, config.LinearRegressionEnabledKey)] - if strings.EqualFold(useLinearRegressionStr, "true") { - modelType = types.LinearRegressor + useLocalRegressorStr := config.ModelConfigValues[getModelConfigKey(powerSourceTarget, config.LocalRegressorEnabledKey)] + if strings.EqualFold(useLocalRegressorStr, "true") { + modelType = types.LocalRegressor return } - // set the default node power model as LinearRegressor + // set the default node power model as LocalRegressor if powerSourceTarget == config.NodePlatformPowerKey || powerSourceTarget == config.NodeComponentsPowerKey { - modelType = types.LinearRegressor + modelType = types.LocalRegressor return } // set the default container power model as Ratio @@ -244,7 +244,7 @@ func getPowerModelEnergySource(powerSourceTarget string) (energySource string) { } // getPowerModelOutputType return the model output type for a given power source, such as platform, components, container or node power sources. -// getPowerModelOutputType only affects LinearRegressor or EstimatorSidecar model. The Ratio model does not download data from the Model Server. +// getPowerModelOutputType only affects LocalRegressor or EstimatorSidecar model. The Ratio model does not download data from the Model Server. // AbsPower for Node, DynPower for container and process func getPowerModelOutputType(powerSourceTarget string) types.ModelOutputType { switch powerSourceTarget { diff --git a/pkg/model/types/types.go b/pkg/model/types/types.go index c56a9e3e60..1be71e5df4 100644 --- a/pkg/model/types/types.go +++ b/pkg/model/types/types.go @@ -18,22 +18,27 @@ package types type ModelType int type ModelOutputType int +type RegressorType int var ( ModelOutputTypeConverter = []string{ "AbsPower", "DynPower", } ModelTypeConverter = []string{ - "Ratio", "LinearRegressor", "EstimatorSidecar", + "Ratio", "LocalRegressor", "EstimatorSidecar", + } + RegressorTypeConverter = []string{ + "LinearRegression", "XGradientBoostingRegressor", } ) const ( // Power Model types Ratio ModelType = iota + 1 // estimation happens within kepler without using Model Server - LinearRegressor // estimation happens within kepler, but pre-trained model parameters are downloaded externally + LocalRegressor // estimation happens within kepler, but pre-trained model parameters are downloaded externally EstimatorSidecar // estimation happens in the sidecar with a loaded pre-trained power model ) + const ( // Power Model Output types // Absolute Power Model (AbsPower): is the power model trained by measured power (including the idle power) @@ -43,6 +48,12 @@ const ( Unsupported ) +const ( + // Regressor types + LinearRegressor RegressorType = iota + 1 + XGBoostRegressor +) + var ( // Define energy source PlatformEnergySource = "acpi" @@ -63,6 +74,13 @@ func (s ModelType) String() string { return "unknown" } +func (r RegressorType) String() string { + if int(r) <= len(RegressorTypeConverter) { + return RegressorTypeConverter[r-1] + } + return "unknown" +} + type ModelConfig struct { // model configuration ModelType ModelType