Skip to content

Commit

Permalink
regressor: support local xgboost regressor
Browse files Browse the repository at this point in the history
Signed-off-by: Huamin Chen <[email protected]>
  • Loading branch information
rootfs committed Nov 8, 2023
1 parent 8c8dab4 commit a09770d
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 65 deletions.
14 changes: 7 additions & 7 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
126 changes: 91 additions & 35 deletions pkg/model/estimator/local/lr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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)
Expand All @@ -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(),
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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")
}
Expand All @@ -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())
}
Expand All @@ -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())
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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
}
25 changes: 14 additions & 11 deletions pkg/model/estimator/local/lr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/

Expand Down Expand Up @@ -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),
}
}

Expand All @@ -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,
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand Down
Loading

0 comments on commit a09770d

Please sign in to comment.