Merge pull request #15 from app2scale/ui_updates
Browse files- agent/dashboard/__init__.py +9 -3
- agent/dashboard/training.py +53 -39
- agent/public/training.drawio +1 -0
- agent/public/training.png +0 -0
agent/dashboard/__init__.py
CHANGED
|
@@ -4,16 +4,22 @@ route_order = ["/","data","training","testing","inference"]
|
|
| 4 |
|
| 5 |
@solara.component
|
| 6 |
def Page():
|
| 7 |
-
with solara.
|
| 8 |
solara.Markdown(md_text="""
|
| 9 |
## Welcome
|
| 10 |
This web page is created to demonstrate the model-based auto-scaling
|
| 11 |
approach developed in "AI-based Auto-Scaling and Tuning" project.
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
* In [Data](/data) tab, you can investigate the raw data.
|
| 14 |
* In [Training](/training), you can build a model on the raw data.
|
| 15 |
* [Testing](/testing) tab is used to evaluate the performance of the trained model.
|
| 16 |
* Finally, [Inference](/inference) tab provides a simulation environment to test the model.
|
| 17 |
-
""")
|
| 18 |
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
@solara.component
|
| 6 |
def Page():
|
| 7 |
+
with solara.Row():
|
| 8 |
solara.Markdown(md_text="""
|
| 9 |
## Welcome
|
| 10 |
This web page is created to demonstrate the model-based auto-scaling
|
| 11 |
approach developed in "AI-based Auto-Scaling and Tuning" project.
|
| 12 |
+
The model-based approach used a historical data containing the state
|
| 13 |
+
of the application such as replica and cpu-limit, as well as some metrics
|
| 14 |
+
measured from Prometheus.
|
| 15 |
|
| 16 |
* In [Data](/data) tab, you can investigate the raw data.
|
| 17 |
* In [Training](/training), you can build a model on the raw data.
|
| 18 |
* [Testing](/testing) tab is used to evaluate the performance of the trained model.
|
| 19 |
* Finally, [Inference](/inference) tab provides a simulation environment to test the model.
|
|
|
|
| 20 |
|
| 21 |
+
### Training
|
| 22 |
+
Training data is obtained from historical data collected from monitoring a target Kubernetes
|
| 23 |
+
deployment.
|
| 24 |
+

|
| 25 |
+
""")
|
agent/dashboard/training.py
CHANGED
|
@@ -39,9 +39,11 @@ def LossPlot(data, render_count):
|
|
| 39 |
"xAxis": {
|
| 40 |
"type": "category",
|
| 41 |
"data": data['epoch'],
|
|
|
|
| 42 |
},
|
| 43 |
"yAxis": {
|
| 44 |
"type": "value",
|
|
|
|
| 45 |
},
|
| 46 |
"series": [
|
| 47 |
{
|
|
@@ -63,14 +65,6 @@ def LossPlot(data, render_count):
|
|
| 63 |
def force_render():
|
| 64 |
local_state.value['render_count'].set(1 + local_state.value['render_count'].value)
|
| 65 |
|
| 66 |
-
@solara.component
|
| 67 |
-
def FilterPanel(df):
|
| 68 |
-
with solara.Column(gap="0px"):
|
| 69 |
-
solara.CrossFilterReport(df, classes=["py-2"])
|
| 70 |
-
for col in ['replica','cpu','expected_tps','previous_tps']:
|
| 71 |
-
if col in df.columns:
|
| 72 |
-
solara.CrossFilterSelect(df, configurable=False, column=col)
|
| 73 |
-
|
| 74 |
@solara.component
|
| 75 |
def ExecutePanel(df):
|
| 76 |
filter, set_filter = solara.use_cross_filter(id(df))
|
|
@@ -112,38 +106,52 @@ def ExecutePanel(df):
|
|
| 112 |
force_render()
|
| 113 |
local_state.value['model'].set(model)
|
| 114 |
solara.Button(label='Train', on_click=trigger_training)
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
|
| 118 |
@solara.component
|
| 119 |
-
def ParameterSelection(df):
|
| 120 |
def select_input_cols(selected_cols):
|
| 121 |
local_state.value['input_cols'].set(selected_cols)
|
| 122 |
def select_output_cols(selected_cols):
|
| 123 |
local_state.value['output_cols'].set(selected_cols)
|
| 124 |
|
| 125 |
-
with solara.
|
| 126 |
-
with solara.
|
| 127 |
-
with solara.
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
| 129 |
values=local_state.value['input_cols'].value,
|
| 130 |
on_value=select_input_cols)
|
| 131 |
-
solara.SelectMultiple(label='Output
|
| 132 |
values=local_state.value['output_cols'].value,
|
| 133 |
on_value=select_output_cols)
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
solara.Select(label="Model", values=["Perceptron","NetSingleHiddenLayer"],
|
| 139 |
value=local_state.value['model_name'].value,
|
| 140 |
on_value=local_state.value['model_name'].set)
|
| 141 |
-
|
|
|
|
|
|
|
| 142 |
solara.Select(label="Loss", values=['mape'],
|
| 143 |
value=local_state.value['loss_name'].value,
|
| 144 |
on_value=local_state.value['loss_name'].set)
|
| 145 |
-
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
solara.SliderFloat(label='Training ratio',
|
| 148 |
value=local_state.value['trn_ratio'].value, min=0, max=1,
|
| 149 |
on_value=local_state.value['trn_ratio'].set,
|
|
@@ -156,23 +164,24 @@ def ParameterSelection(df):
|
|
| 156 |
value=local_state.value['batch_size_val'].value, min=1, max=256,
|
| 157 |
on_value=local_state.value['batch_size_val'].set,
|
| 158 |
thumb_label=True)
|
| 159 |
-
solara.SliderInt(label='
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
solara.
|
| 168 |
-
|
| 169 |
-
|
|
|
|
| 170 |
|
| 171 |
-
|
| 172 |
|
| 173 |
@solara.component
|
| 174 |
def Page():
|
| 175 |
df = state.value['data']
|
|
|
|
| 176 |
dff = df
|
| 177 |
filtered_cols = []
|
| 178 |
if len(local_state.value['input_cols'].value) > 0:
|
|
@@ -181,10 +190,15 @@ def Page():
|
|
| 181 |
filtered_cols += local_state.value['output_cols'].value
|
| 182 |
if len(filtered_cols) > 0:
|
| 183 |
dff = df[filtered_cols]
|
| 184 |
-
with solara.
|
| 185 |
-
ParameterSelection(
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
|
|
|
|
| 39 |
"xAxis": {
|
| 40 |
"type": "category",
|
| 41 |
"data": data['epoch'],
|
| 42 |
+
"name": "epoch",
|
| 43 |
},
|
| 44 |
"yAxis": {
|
| 45 |
"type": "value",
|
| 46 |
+
"name": "loss",
|
| 47 |
},
|
| 48 |
"series": [
|
| 49 |
{
|
|
|
|
| 65 |
def force_render():
|
| 66 |
local_state.value['render_count'].set(1 + local_state.value['render_count'].value)
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
@solara.component
|
| 69 |
def ExecutePanel(df):
|
| 70 |
filter, set_filter = solara.use_cross_filter(id(df))
|
|
|
|
| 106 |
force_render()
|
| 107 |
local_state.value['model'].set(model)
|
| 108 |
solara.Button(label='Train', on_click=trigger_training)
|
| 109 |
+
with solara.Card(title="Loss History", margin=1, elevation=10,
|
| 110 |
+
subtitle="""Once you start training, you can monitor the training and validation losses in this plot.
|
| 111 |
+
"""):
|
| 112 |
+
LossPlot(local_state.value['loss_plot_data'].value, local_state.value['render_count'].value)
|
| 113 |
|
| 114 |
|
| 115 |
@solara.component
|
| 116 |
+
def ParameterSelection(df, attributes):
|
| 117 |
def select_input_cols(selected_cols):
|
| 118 |
local_state.value['input_cols'].set(selected_cols)
|
| 119 |
def select_output_cols(selected_cols):
|
| 120 |
local_state.value['output_cols'].set(selected_cols)
|
| 121 |
|
| 122 |
+
with solara.lab.Tabs():
|
| 123 |
+
with solara.lab.Tab("I/O"):
|
| 124 |
+
with solara.Card(title="Input/Output Selection", margin=1, elevation=10,
|
| 125 |
+
subtitle="""Select which attributes of the data are to be used as input/output of the
|
| 126 |
+
machine learning model. Selected attributes will be reflected to the dataframe on the right,
|
| 127 |
+
immediately."""):
|
| 128 |
+
solara.SelectMultiple(label='Input features', all_values=attributes,
|
| 129 |
values=local_state.value['input_cols'].value,
|
| 130 |
on_value=select_input_cols)
|
| 131 |
+
solara.SelectMultiple(label='Output features', all_values=attributes,
|
| 132 |
values=local_state.value['output_cols'].value,
|
| 133 |
on_value=select_output_cols)
|
| 134 |
+
with solara.lab.Tab("PARAMETERS"):
|
| 135 |
+
with solara.Card(title="Model Training Parameters", margin=1, elevation=10,
|
| 136 |
+
subtitle="""Select the machine learning model, optimizer, loss and various hyper-parameters
|
| 137 |
+
used in the training."""):
|
| 138 |
solara.Select(label="Model", values=["Perceptron","NetSingleHiddenLayer"],
|
| 139 |
value=local_state.value['model_name'].value,
|
| 140 |
on_value=local_state.value['model_name'].set)
|
| 141 |
+
solara.Select(label="Optimizer", values=["Adam"],
|
| 142 |
+
value=local_state.value['optimizer_name'].value,
|
| 143 |
+
on_value=local_state.value['optimizer_name'].set)
|
| 144 |
solara.Select(label="Loss", values=['mape'],
|
| 145 |
value=local_state.value['loss_name'].value,
|
| 146 |
on_value=local_state.value['loss_name'].set)
|
| 147 |
+
solara.SliderFloat(label="Learning rate (log10)",
|
| 148 |
+
value=local_state.value['learning_rate_log10'].value,
|
| 149 |
+
min=-4, max=1, step=0.01,
|
| 150 |
+
on_value=local_state.value['learning_rate_log10'].set)
|
| 151 |
+
solara.SliderInt(label='Max epoch',
|
| 152 |
+
value=local_state.value['max_epoch'].value, min=1, max=1000,
|
| 153 |
+
on_value=local_state.value['max_epoch'].set,
|
| 154 |
+
thumb_label=True)
|
| 155 |
solara.SliderFloat(label='Training ratio',
|
| 156 |
value=local_state.value['trn_ratio'].value, min=0, max=1,
|
| 157 |
on_value=local_state.value['trn_ratio'].set,
|
|
|
|
| 164 |
value=local_state.value['batch_size_val'].value, min=1, max=256,
|
| 165 |
on_value=local_state.value['batch_size_val'].set,
|
| 166 |
thumb_label=True)
|
| 167 |
+
solara.SliderInt(label='random seed', value=local_state.value['seed'].value, min=0, max=1000,
|
| 168 |
+
on_value=local_state.value['seed'].set,
|
| 169 |
+
thumb_label=True)
|
| 170 |
+
with solara.lab.Tab("FILTER"):
|
| 171 |
+
with solara.Card(title="Data Filter", margin=1, elevation=10,
|
| 172 |
+
subtitle="""In addition to the input/output attributes, you can also
|
| 173 |
+
select a subset of rows for training. Filtered dataframe displayed on the
|
| 174 |
+
right will be used in the training."""):
|
| 175 |
+
solara.CrossFilterReport(df)
|
| 176 |
+
for col in ['replica','cpu','expected_tps','previous_tps']:
|
| 177 |
+
if col in df.columns:
|
| 178 |
+
solara.CrossFilterSelect(df, configurable=False, column=col)
|
| 179 |
|
|
|
|
| 180 |
|
| 181 |
@solara.component
|
| 182 |
def Page():
|
| 183 |
df = state.value['data']
|
| 184 |
+
attributes = list(df.columns)
|
| 185 |
dff = df
|
| 186 |
filtered_cols = []
|
| 187 |
if len(local_state.value['input_cols'].value) > 0:
|
|
|
|
| 190 |
filtered_cols += local_state.value['output_cols'].value
|
| 191 |
if len(filtered_cols) > 0:
|
| 192 |
dff = df[filtered_cols]
|
| 193 |
+
with solara.Sidebar():
|
| 194 |
+
ParameterSelection(dff, attributes)
|
| 195 |
+
with solara.ColumnsResponsive():
|
| 196 |
+
ExecutePanel(dff)
|
| 197 |
+
with solara.Card(title="Training/Testing Data", margin=1, elevation=10,
|
| 198 |
+
subtitle="""Based on the selected nput/output attributes and the cross-filters,
|
| 199 |
+
this is the final data used in training/validation. Right before using
|
| 200 |
+
this data in the training/validation, normalization is applied. For more
|
| 201 |
+
information please check backend.data.ExplorationDataset"""):
|
| 202 |
+
solara.CrossFilterDataFrame(dff, items_per_page=10)
|
| 203 |
|
| 204 |
|
agent/public/training.drawio
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<mxfile host="Electron" modified="2024-04-24T09:20:44.239Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.7.4 Chrome/106.0.5249.199 Electron/21.3.3 Safari/537.36" version="20.7.4" etag="chSOnRKB6Z1sgUmyfTrz" type="device"><diagram id="4Ei_wvUUD_S_ACNlq_Ix" name="Page-1">7Vnbcto6FP0aHpvxBdvwyDWlA4EGOuexI2zZ1olseWQRQr++ki1fRU6ghdIzA5kJ0tLWlqy1vKUtOuYoenukIAkXxIO4Y2jeW8ccdwxDt/Ue/xLIIUesXj8HAoo8aVQBa/QDSlCT6A55MG0YMkIwQ0kTdEkcQ5c1MEAp2TfNfIKboyYggAqwdgFW0X+Qx8Ic7VlahX+GKAiLkXVNtkSgMJZAGgKP7GuQOemYI0oIy0vR2whisXjFuuT9pu+0lhOjMGandPi6wxN3M58Pv2ub749P9Ml1g0+WZOMV4J184tXzZDwbbWbLp45hY+56uKW8FIjSYjmezOXjsEOxRpTsYg+KYTRusw8Rg+sEuKJ1z1XBsZBFmNd0XvRJzKYgQlgI4gtkQwpQnHKHCxIT3l4uU2G8lgNldYTxiGBCs4FN3xJ/0q6G5x/hjFHyAmstdvbhLfKxIWXw7d0F1UuauL4hiSCjB24iO3zqFlxLbdtdWd/XlCKhsCaSwgxIbQal64o+XpAMnsGmrSlsPk9W89lo8Pdw5lmw53WPcdMztubFuLHsFjfmadwYV+PGULgZrb5xYD5bzDZ3fqxb82Mq/MyXg/GdGOfWxHTfeXG+rQePk7+HH9+Htuse48dz+ltNuxA/5e5RBjbrxvxYCj+LyeLOTxnYbs2PrfCzWa3vvDg35sVxFA6gx3MPWSWUhSQgMcCTCh02Waps5oQkcnn/hYwdZCIFdoycxxyMvYFIm3jrFhP3JYemSDzbuOJCzPRcJijEgKHXZr9jyyq7rgjiHisGS8Ykg47WoiYlO+pC2a2eCbU9WUZTC47e8sQADSBTPPGVAYeaWSIM0nOm3NXOnFmzAy/kc6hkVy7wbyhR3WE/z9ab5TNPHOZqEjgebNR8gr/xiSi6B4y4Qqn5cRDZ5lqeb0sAuC9BpvDljnE3RcBIczHr1gn6bR+aAOz5R4OL7fbg1r9QcHFatNmGGlz6R4KLc7Xgom7KarRRX/UaO81Ic0LYuEiMqK2XdWS9Cux3Q0nXafFl/2oo6eofeHonlFzs3VV39zvRFT3mR/ScTHQ5pVsR3bsfF86i3tL7D5p5dDf9Bfa1P3RiUAb6IwcA9RZ4zQCDYuJicuoZgKFItDJFk3wTZU0JNXfemIhtvbFNSwhgFMTiBMGFATk+FFsycgEeyIYIeV6m6mPniv+IYoNXGCPaSkSMop4pgAMhYwknxjL52cbiyy3+CYP0ISAkwBAkKH1wSZQ1uGlmOvXzMXixGuUS54n2JYxVHB5udbPcU28vF9wxctNTNDLU7zq5ik7aSa3VPy2pvZ5O1FvUhJLX7Dc83jVOdlwOU7JjWUHzAANXkAaGPvv/CiPzKlOeS91+2K0Db5mw1ISiX+j6g1erXzXzLar6bdic/AQ=</diagram></mxfile>
|
agent/public/training.png
ADDED
|