Commit
·
a89cb16
1
Parent(s):
15d07dc
ui: initial files
Browse files- Dockerfile +10 -0
- agent/__init__.py +3 -0
- agent/backend/__pycache__/data.cpython-310.pyc +0 -0
- agent/backend/__pycache__/loss.cpython-310.pyc +0 -0
- agent/backend/__pycache__/models.cpython-310.pyc +0 -0
- agent/backend/__pycache__/utils.cpython-310.pyc +0 -0
- agent/backend/data.py +101 -0
- agent/backend/loss.py +4 -0
- agent/backend/models.py +35 -0
- agent/backend/utils.py +113 -0
- agent/dashboard/__init__.py +8 -0
- agent/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
- agent/dashboard/__pycache__/data.cpython-310.pyc +0 -0
- agent/dashboard/__pycache__/settings.cpython-310.pyc +0 -0
- agent/dashboard/__pycache__/training.cpython-310.pyc +0 -0
- agent/dashboard/data.py +93 -0
- agent/dashboard/training.py +172 -0
- agent/data/averaged_full_state_data.csv +0 -0
- mypy.ini +3 -0
- pyproject.toml +30 -0
Dockerfile
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10
|
| 2 |
+
RUN useradd -m -u 1000 user
|
| 3 |
+
USER user
|
| 4 |
+
ENV HOME=/home/user \
|
| 5 |
+
PATH=/home/user/.local/bin:$PATH
|
| 6 |
+
COPY --chown=user . $HOME/app
|
| 7 |
+
WORKDIR $HOME/app
|
| 8 |
+
RUN (cd agent & pip install -e .)
|
| 9 |
+
|
| 10 |
+
CMD ["solara", "run", "agent.dashboard", "--host", "0.0.0.0", "--port", "7860"]
|
agent/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""APP2SCALE agent package"""
|
| 2 |
+
__title__ = "Intelligent agent and its dashboard"
|
| 3 |
+
__version__ = "0.0.1"
|
agent/backend/__pycache__/data.cpython-310.pyc
ADDED
|
Binary file (3.57 kB). View file
|
|
|
agent/backend/__pycache__/loss.cpython-310.pyc
ADDED
|
Binary file (346 Bytes). View file
|
|
|
agent/backend/__pycache__/models.cpython-310.pyc
ADDED
|
Binary file (1.72 kB). View file
|
|
|
agent/backend/__pycache__/utils.cpython-310.pyc
ADDED
|
Binary file (3.2 kB). View file
|
|
|
agent/backend/data.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import torch
|
| 3 |
+
from torch.utils.data import Dataset
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
class ExplorationDataset(Dataset):
|
| 7 |
+
def __init__(self, df: pd.DataFrame,
|
| 8 |
+
input_cols,
|
| 9 |
+
output_cols):
|
| 10 |
+
super().__init__()
|
| 11 |
+
self.df = df
|
| 12 |
+
self.input_cols = input_cols
|
| 13 |
+
self.output_cols = output_cols
|
| 14 |
+
self.transform_dict = self.transform_fit(df)
|
| 15 |
+
self.input_transformed = torch.tensor(self.input_transform(df[input_cols]).values).to(torch.float)
|
| 16 |
+
self.output_transformed = torch.tensor(self.output_transform(df[output_cols]).values).to(torch.float)
|
| 17 |
+
|
| 18 |
+
def __getitem__(self, idx: int) -> tuple[torch.tensor, torch.tensor]:
|
| 19 |
+
inputs = self.input_transformed[idx]
|
| 20 |
+
outputs = self.output_transformed[idx]
|
| 21 |
+
return inputs, outputs
|
| 22 |
+
|
| 23 |
+
def __len__(self) -> int:
|
| 24 |
+
return len(self.df)
|
| 25 |
+
|
| 26 |
+
def transform_fit(self, df):
|
| 27 |
+
transform_dict = {}
|
| 28 |
+
for f in self.input_cols + self.output_cols:
|
| 29 |
+
if f in ['replica','cpu', 'heap']:
|
| 30 |
+
shift = df[f].median()
|
| 31 |
+
divide = 1
|
| 32 |
+
logtransform = False
|
| 33 |
+
elif f in ['expected_tps']:
|
| 34 |
+
shift = df[f].mean()
|
| 35 |
+
divide = df[f].std()
|
| 36 |
+
logtransform = False
|
| 37 |
+
elif f in ['num_request']:
|
| 38 |
+
shift = 0
|
| 39 |
+
divide = df[f].max()
|
| 40 |
+
logtransform = False
|
| 41 |
+
elif f in ['response_time']:
|
| 42 |
+
# shift/divide is only after log
|
| 43 |
+
logtransform = True
|
| 44 |
+
shift = 0
|
| 45 |
+
divide = np.max(np.log10(df[f]))
|
| 46 |
+
else:
|
| 47 |
+
shift = 0
|
| 48 |
+
divide = 1
|
| 49 |
+
logtransform = False
|
| 50 |
+
|
| 51 |
+
transform_dict[f] = {'shift': shift, 'divide': divide,'logtransform': logtransform}
|
| 52 |
+
return transform_dict
|
| 53 |
+
|
| 54 |
+
def transform(self, df, cols):
|
| 55 |
+
df_transform = df.copy(deep=True)
|
| 56 |
+
for f in df.columns:
|
| 57 |
+
if f in cols:
|
| 58 |
+
shift = self.transform_dict[f]['shift']
|
| 59 |
+
divide = self.transform_dict[f]['divide']
|
| 60 |
+
logtransform = self.transform_dict[f]['logtransform']
|
| 61 |
+
if logtransform:
|
| 62 |
+
df_transform[f] = np.log10(df_transform[f])
|
| 63 |
+
df_transform[f] = (df_transform[f] - shift) / divide
|
| 64 |
+
return df_transform
|
| 65 |
+
|
| 66 |
+
def inv_transform(self, df, cols):
|
| 67 |
+
df_transform = df.copy(deep=True)
|
| 68 |
+
for f in df.columns:
|
| 69 |
+
if f in cols:
|
| 70 |
+
shift = self.transform_dict[f]['shift']
|
| 71 |
+
divide = self.transform_dict[f]['divide']
|
| 72 |
+
logtransform = self.transform_dict[f]['logtransform']
|
| 73 |
+
df_transform[f] = divide * df_transform[f] + shift
|
| 74 |
+
if logtransform:
|
| 75 |
+
df_transform[f] = np.power(df_transform[f], 10)
|
| 76 |
+
return df_transform
|
| 77 |
+
|
| 78 |
+
def input_transform(self, df: pd.DataFrame) -> pd.DataFrame:
|
| 79 |
+
return self.transform(df, self.input_cols)
|
| 80 |
+
|
| 81 |
+
def output_transform(self, df: pd.DataFrame) -> pd.DataFrame:
|
| 82 |
+
return self.transform(df, self.output_cols)
|
| 83 |
+
|
| 84 |
+
def input_inv_transform(self, df: pd.DataFrame) -> pd.DataFrame:
|
| 85 |
+
return self.inv_transform(df, self.input_cols)
|
| 86 |
+
|
| 87 |
+
def output_inv_transform(self, df: pd.DataFrame) -> pd.DataFrame:
|
| 88 |
+
return self.inv_transform(df, self.output_cols)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def data_replica_vs_cpu_usage():
|
| 92 |
+
'''Replica versus cpu_usage'''
|
| 93 |
+
df = pd.read_csv('averaged_full_state_data.csv')
|
| 94 |
+
df.query('cpu == 5 and expected_tps == 88')
|
| 95 |
+
input_cols = ['replica']
|
| 96 |
+
output_cols = ['cpu_usage']
|
| 97 |
+
other_cols = []
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
df = df[input_cols + output_cols + other_cols]
|
| 101 |
+
return df, input_cols, output_cols, other_cols
|
agent/backend/loss.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
def loss_mape(output, target):
|
| 4 |
+
return torch.mean(torch.abs((target - output) / target))
|
agent/backend/models.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn as nn
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
class Perceptron(nn.Module):
|
| 5 |
+
def __init__(self, in_features: int, out_features: int):
|
| 6 |
+
super().__init__()
|
| 7 |
+
self.in_features = in_features
|
| 8 |
+
self.out_features = out_features
|
| 9 |
+
self.layer = nn.Sequential(nn.Linear(in_features=self.in_features,out_features=self.out_features))
|
| 10 |
+
|
| 11 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 12 |
+
out = self.layer(x)
|
| 13 |
+
return out
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class PolicyGradientNetwork(nn.Module):
|
| 17 |
+
def __init__(self, num_inputs, num_actions, hidden_size):
|
| 18 |
+
super(PolicyGradientNetwork, self).__init__()
|
| 19 |
+
|
| 20 |
+
self.num_actions = num_actions
|
| 21 |
+
self.norm0 = nn.BatchNorm1d(num_features=num_inputs)
|
| 22 |
+
self.linear1 = nn.Linear(num_inputs, hidden_size)
|
| 23 |
+
self.linear2 = nn.Linear(hidden_size, hidden_size)
|
| 24 |
+
self.linear_act = nn.Linear(hidden_size, int(num_actions))
|
| 25 |
+
|
| 26 |
+
# a neural network two hidden layers with the same size
|
| 27 |
+
def forward(self, state):
|
| 28 |
+
x = self.norm0(state)
|
| 29 |
+
x = torch.nn.functional.gelu(self.linear1(x))
|
| 30 |
+
x = torch.nn.functional.relu(self.linear2(x))
|
| 31 |
+
x_act = self.linear_act(x)
|
| 32 |
+
x_act = torch.nn.functional.softmax(x_act, dim=1)
|
| 33 |
+
|
| 34 |
+
# return the probability list of the actions
|
| 35 |
+
return x_act
|
agent/backend/utils.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from tqdm import tqdm
|
| 3 |
+
from torch.utils.data import Dataset, DataLoader
|
| 4 |
+
from functools import partial
|
| 5 |
+
|
| 6 |
+
from .data import ExplorationDataset
|
| 7 |
+
from .models import Perceptron
|
| 8 |
+
from .loss import loss_mape
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def train(df, model_name, input_cols, output_cols, trn_ratio,
|
| 13 |
+
batch_size_trn, batch_size_val, optimizer_name, learning_rate,
|
| 14 |
+
max_epoch, loss_name):
|
| 15 |
+
if model_name == "Perceptron":
|
| 16 |
+
model = Perceptron(in_features=len(input_cols), out_features=len(output_cols))
|
| 17 |
+
if loss_name == "mape":
|
| 18 |
+
loss_fn = loss_mape
|
| 19 |
+
ds = ExplorationDataset(df, input_cols=input_cols, output_cols=output_cols)
|
| 20 |
+
|
| 21 |
+
trn_size = int(len(ds)*trn_ratio)
|
| 22 |
+
val_size = len(ds) - trn_size
|
| 23 |
+
ds_trn, ds_val = torch.utils.data.random_split(ds, [trn_size, val_size])
|
| 24 |
+
dl_trn = DataLoader(ds_trn, batch_size=batch_size_trn, shuffle=True)
|
| 25 |
+
dl_val = DataLoader(ds_val, batch_size=batch_size_val, shuffle=True)
|
| 26 |
+
|
| 27 |
+
if optimizer_name == "Adam":
|
| 28 |
+
optimizer_fn = partial(torch.optim.Adam,lr=learning_rate)
|
| 29 |
+
print('backend training ...')
|
| 30 |
+
print('training in progress...', len(df))
|
| 31 |
+
print('data columns', list(df.columns))
|
| 32 |
+
print('input columns', input_cols)
|
| 33 |
+
print('output columns', output_cols)
|
| 34 |
+
print('training ratio', trn_ratio)
|
| 35 |
+
print('batch size trainig', batch_size_trn)
|
| 36 |
+
print('batch size validation', batch_size_val)
|
| 37 |
+
print(f'Number of samples {len(ds)}')
|
| 38 |
+
print(f'Number of samples in training {len(ds_trn)}')
|
| 39 |
+
print(f'Number of samples in validation {len(ds_val)}')
|
| 40 |
+
print(f'Learning rate: {learning_rate}')
|
| 41 |
+
print(f'Optimizer {optimizer_name}')
|
| 42 |
+
print(f'Max epoch: {max_epoch}')
|
| 43 |
+
|
| 44 |
+
x, y = ds[0]
|
| 45 |
+
in_features = x.shape[0]
|
| 46 |
+
out_features = y.shape[0]
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
optimizer = optimizer_fn(model.parameters())
|
| 50 |
+
|
| 51 |
+
#epochbar = tqdm(range(max_epoch))
|
| 52 |
+
for ep in range(max_epoch):
|
| 53 |
+
model.train()
|
| 54 |
+
for x, y in dl_trn:
|
| 55 |
+
optimizer.zero_grad()
|
| 56 |
+
y_pred = model(x)
|
| 57 |
+
loss = loss_fn(y_pred, y)
|
| 58 |
+
loss.backward()
|
| 59 |
+
optimizer.step()
|
| 60 |
+
|
| 61 |
+
trn_loss = evaluate(model, dl_trn, loss_fn)
|
| 62 |
+
val_loss = evaluate(model, dl_val, loss_fn)
|
| 63 |
+
#epochbar.set_postfix(epoch=ep+1,loss=loss.item(),val_loss=val_loss)
|
| 64 |
+
yield ep, trn_loss, val_loss, None
|
| 65 |
+
|
| 66 |
+
return ep, trn_loss, val_loss, model
|
| 67 |
+
|
| 68 |
+
def predict(model, dataloader):
|
| 69 |
+
with torch.no_grad():
|
| 70 |
+
predictions = torch.empty(0, model.out_features)
|
| 71 |
+
targets = torch.empty(predictions.shape)
|
| 72 |
+
for x, y in dataloader:
|
| 73 |
+
y_pred = model.forward(x)
|
| 74 |
+
predictions = torch.cat([predictions, y_pred], dim=0)
|
| 75 |
+
targets = torch.cat([targets, y], dim=0)
|
| 76 |
+
return predictions, targets
|
| 77 |
+
|
| 78 |
+
def evaluate(model, dataloader, loss_fn):
|
| 79 |
+
with torch.no_grad():
|
| 80 |
+
avg_loss = 0
|
| 81 |
+
for x, y in dataloader:
|
| 82 |
+
y_pred = model.forward(x)
|
| 83 |
+
loss = loss_fn(y_pred, y)
|
| 84 |
+
avg_loss += loss.item()
|
| 85 |
+
avg_loss = avg_loss / len(dataloader)
|
| 86 |
+
return avg_loss
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def update_policy(model, rewards, log_probabilities, gamma, learning_rate, optimizer):
|
| 90 |
+
discounted_rewards = []
|
| 91 |
+
|
| 92 |
+
for t in range(len(rewards)):
|
| 93 |
+
gt = 0
|
| 94 |
+
pw = 0
|
| 95 |
+
for r in rewards[t:]:
|
| 96 |
+
gt = gt + gamma ** pw * r
|
| 97 |
+
pw = pw + 1
|
| 98 |
+
discounted_rewards.append(gt)
|
| 99 |
+
|
| 100 |
+
discounted_rewards = torch.tensor(discounted_rewards)
|
| 101 |
+
# normalize discounted rewards
|
| 102 |
+
discounted_rewards = (discounted_rewards - discounted_rewards.mean()) / (discounted_rewards.std(0) + 1e-9)
|
| 103 |
+
|
| 104 |
+
policy_gradient = []
|
| 105 |
+
for log_probability, gt in zip(log_probabilities, discounted_rewards):
|
| 106 |
+
policy_gradient.append(-log_probability * gt)
|
| 107 |
+
# policy_gradient.append(1.0 / log_probability * gt)
|
| 108 |
+
|
| 109 |
+
model.optimizer.zero_grad()
|
| 110 |
+
policy_gradient = torch.stack(policy_gradient).sum()
|
| 111 |
+
# policy_gradient.backward()
|
| 112 |
+
policy_gradient.backward(retain_graph=True)
|
| 113 |
+
optimizer.step()
|
agent/dashboard/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import solara
|
| 2 |
+
|
| 3 |
+
@solara.component
|
| 4 |
+
def Page():
|
| 5 |
+
with solara.VBox() as main:
|
| 6 |
+
solara.Text("Home")
|
| 7 |
+
|
| 8 |
+
return main
|
agent/dashboard/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (406 Bytes). View file
|
|
|
agent/dashboard/__pycache__/data.cpython-310.pyc
ADDED
|
Binary file (2.7 kB). View file
|
|
|
agent/dashboard/__pycache__/settings.cpython-310.pyc
ADDED
|
Binary file (349 Bytes). View file
|
|
|
agent/dashboard/__pycache__/training.cpython-310.pyc
ADDED
|
Binary file (4.85 kB). View file
|
|
|
agent/dashboard/data.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import solara
|
| 2 |
+
import pandas as pd
|
| 3 |
+
from typing import Optional, cast
|
| 4 |
+
import solara.express as solara_px
|
| 5 |
+
|
| 6 |
+
df = pd.read_csv('agent/data/averaged_full_state_data.csv')
|
| 7 |
+
|
| 8 |
+
state = solara.reactive(
|
| 9 |
+
{
|
| 10 |
+
'data': df ,
|
| 11 |
+
'x': solara.reactive('expected_tps'),
|
| 12 |
+
'y': solara.reactive('avg_num_request'),
|
| 13 |
+
'logx': solara.reactive(False),
|
| 14 |
+
'logy': solara.reactive(False),
|
| 15 |
+
'size_max': solara.reactive(10.0),
|
| 16 |
+
'size': solara.reactive('replica'),
|
| 17 |
+
'color': solara.reactive('cpu_usage'),
|
| 18 |
+
'filter': solara.reactive(None),
|
| 19 |
+
}
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
@solara.component
|
| 23 |
+
def FilteredDataFrame(df):
|
| 24 |
+
filter = state.value['filter'].value
|
| 25 |
+
|
| 26 |
+
dff = df
|
| 27 |
+
if filter is not None:
|
| 28 |
+
dff = df[filter]
|
| 29 |
+
solara.DataFrame(dff, items_per_page=10)
|
| 30 |
+
|
| 31 |
+
@solara.component
|
| 32 |
+
def FilterPanel(df):
|
| 33 |
+
solara.CrossFilterReport(df, classes=["py-2"])
|
| 34 |
+
solara.CrossFilterSelect(df, configurable=False, column='replica')
|
| 35 |
+
solara.CrossFilterSelect(df, configurable=False, column='cpu')
|
| 36 |
+
solara.CrossFilterSelect(df, configurable=False, column='expected_tps')
|
| 37 |
+
solara.CrossFilterSelect(df, configurable=False, column='previous_tps')
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@solara.component
|
| 42 |
+
def ExecutionPanel():
|
| 43 |
+
solara.Text("Execution Panel")
|
| 44 |
+
|
| 45 |
+
@solara.component
|
| 46 |
+
def DataViewer(df):
|
| 47 |
+
input_cols, set_input_cols = solara.use_state(['replica'])
|
| 48 |
+
|
| 49 |
+
filter = state.value['filter'].value
|
| 50 |
+
|
| 51 |
+
dff = df
|
| 52 |
+
if filter is not None:
|
| 53 |
+
dff = df[filter]
|
| 54 |
+
|
| 55 |
+
with solara.Sidebar():
|
| 56 |
+
FilterPanel(df)
|
| 57 |
+
with solara.Card("Controls", margin=0, elevation=0):
|
| 58 |
+
with solara.Column():
|
| 59 |
+
columns = list(df.columns)
|
| 60 |
+
solara.SliderFloat(label="Size Max", value=state.value['size_max'], min=1, max=100, on_value=state.value['size_max'].set)
|
| 61 |
+
solara.Checkbox(label="Log x", value=state.value['logx'], on_value=state.value['logx'].set)
|
| 62 |
+
solara.Checkbox(label="Log y", value=state.value['logy'], on_value=state.value['logy'].set)
|
| 63 |
+
solara.Select("Size", values=columns, value=state.value['size'].value, on_value=state.value['size'].set)
|
| 64 |
+
solara.Select("Color", values=columns, value=state.value['color'].value, on_value=state.value['color'].set)
|
| 65 |
+
solara.Select("Column x", values=columns, value=state.value['x'].value, on_value=state.value['x'].set)
|
| 66 |
+
solara.Select("Column y", values=columns, value=state.value['y'].value, on_value=state.value['y'].set)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
solara.CrossFilterDataFrame(df, items_per_page=10)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
if state.value['x'].value and state.value['y'].value:
|
| 73 |
+
solara_px.scatter(
|
| 74 |
+
dff,
|
| 75 |
+
state.value['x'].value,
|
| 76 |
+
state.value['y'].value,
|
| 77 |
+
size=state.value['size'].value,
|
| 78 |
+
color=state.value['color'].value,
|
| 79 |
+
size_max=state.value['size_max'].value,
|
| 80 |
+
log_x=state.value['logx'].value,
|
| 81 |
+
log_y=state.value['logy'].value,
|
| 82 |
+
)
|
| 83 |
+
else:
|
| 84 |
+
solara.Warning("Select x and y columns")
|
| 85 |
+
|
| 86 |
+
@solara.component
|
| 87 |
+
def Page():
|
| 88 |
+
#if state.value['filter'].value is None:
|
| 89 |
+
# print('setting....')
|
| 90 |
+
# filter, set_filter = solara.use_cross_filter(id(state.value['data']))
|
| 91 |
+
# state.value['filter'].set(filter)
|
| 92 |
+
|
| 93 |
+
DataViewer(state.value['data'])
|
agent/dashboard/training.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import solara
|
| 2 |
+
import pandas as pd
|
| 3 |
+
from typing import Optional, cast
|
| 4 |
+
import solara.express as solara_px
|
| 5 |
+
from .data import state
|
| 6 |
+
from ..backend.utils import train
|
| 7 |
+
from ..backend.loss import loss_mape
|
| 8 |
+
|
| 9 |
+
local_state = solara.reactive(
|
| 10 |
+
{
|
| 11 |
+
'input_cols': solara.reactive(['replica']),
|
| 12 |
+
'output_cols': solara.reactive(['cpu_usage']),
|
| 13 |
+
'trn_ratio' : solara.reactive(0.8),
|
| 14 |
+
'learning_rate_log10': solara.reactive(-3),
|
| 15 |
+
'batch_size_trn': solara.reactive(32),
|
| 16 |
+
'batch_size_val': solara.reactive(16),
|
| 17 |
+
'model_name': solara.reactive("Perceptron"),
|
| 18 |
+
'optimizer_name': solara.reactive("Adam"),
|
| 19 |
+
'max_epoch': solara.reactive(100),
|
| 20 |
+
'loss_name': solara.reactive('mape'),
|
| 21 |
+
'loss_plot_data': solara.reactive({'epoch': [], 'trn_loss': [], 'val_loss': []}),
|
| 22 |
+
'render_count': solara.reactive(0),
|
| 23 |
+
'model': solara.reactive(None),
|
| 24 |
+
}
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
@solara.component
|
| 28 |
+
def LossPlot(data, render_count):
|
| 29 |
+
options = {
|
| 30 |
+
"xAxis": {
|
| 31 |
+
"type": "category",
|
| 32 |
+
"data": data['epoch'],
|
| 33 |
+
},
|
| 34 |
+
"yAxis": {
|
| 35 |
+
"type": "value",
|
| 36 |
+
},
|
| 37 |
+
"series": [
|
| 38 |
+
{
|
| 39 |
+
"data": data['trn_loss'],
|
| 40 |
+
"type": 'line'
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"data": data['val_loss'],
|
| 44 |
+
"type": 'line'
|
| 45 |
+
},
|
| 46 |
+
]
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
with solara.Column():
|
| 50 |
+
solara.FigureEcharts(option=options)
|
| 51 |
+
|
| 52 |
+
def force_render():
|
| 53 |
+
local_state.value['render_count'].set(1 + local_state.value['render_count'].value)
|
| 54 |
+
|
| 55 |
+
@solara.component
|
| 56 |
+
def FilterPanel(df):
|
| 57 |
+
solara.CrossFilterReport(df, classes=["py-2"])
|
| 58 |
+
for col in ['replica','cpu','expected_tps','previous_tps']:
|
| 59 |
+
if col in df.columns:
|
| 60 |
+
solara.CrossFilterSelect(df, configurable=False, column=col)
|
| 61 |
+
|
| 62 |
+
@solara.component
|
| 63 |
+
def ExecutePanel(df):
|
| 64 |
+
filter, set_filter = solara.use_cross_filter(id(df))
|
| 65 |
+
|
| 66 |
+
dff = df
|
| 67 |
+
if filter is not None:
|
| 68 |
+
dff = df[filter]
|
| 69 |
+
|
| 70 |
+
def trigger_training():
|
| 71 |
+
|
| 72 |
+
input_cols = local_state.value['input_cols'].value
|
| 73 |
+
output_cols = local_state.value['output_cols'].value
|
| 74 |
+
trn_ratio = local_state.value['trn_ratio'].value
|
| 75 |
+
batch_size_trn = local_state.value['batch_size_trn'].value
|
| 76 |
+
batch_size_val = local_state.value['batch_size_val'].value
|
| 77 |
+
learning_rate_log10 = local_state.value['learning_rate_log10'].value
|
| 78 |
+
learning_rate = 10**learning_rate_log10
|
| 79 |
+
optimizer_name = local_state.value['optimizer_name'].value
|
| 80 |
+
max_epoch = local_state.value['max_epoch'].value
|
| 81 |
+
loss_name = local_state.value['loss_name'].value
|
| 82 |
+
|
| 83 |
+
epoch_list = []
|
| 84 |
+
trn_loss_list = []
|
| 85 |
+
val_loss_list = []
|
| 86 |
+
for epoch, trn_loss, val_loss, model in train(dff, "Perceptron", input_cols, output_cols, trn_ratio,
|
| 87 |
+
batch_size_trn, batch_size_val, optimizer_name, learning_rate,
|
| 88 |
+
max_epoch, loss_name):
|
| 89 |
+
epoch_list.append(epoch)
|
| 90 |
+
trn_loss_list.append(trn_loss)
|
| 91 |
+
val_loss_list.append(val_loss)
|
| 92 |
+
local_state.value['loss_plot_data'].set(
|
| 93 |
+
{'epoch':epoch_list,
|
| 94 |
+
'trn_loss': trn_loss_list,
|
| 95 |
+
'val_loss': val_loss_list})
|
| 96 |
+
force_render()
|
| 97 |
+
local_state.value['model'].set(model)
|
| 98 |
+
solara.Button(label='Train', on_click=trigger_training)
|
| 99 |
+
LossPlot(local_state.value['loss_plot_data'].value, local_state.value['render_count'].value)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
@solara.component
|
| 103 |
+
def ParameterSelection(df):
|
| 104 |
+
def select_input_cols(selected_cols):
|
| 105 |
+
local_state.value['input_cols'].set(selected_cols)
|
| 106 |
+
def select_output_cols(selected_cols):
|
| 107 |
+
local_state.value['output_cols'].set(selected_cols)
|
| 108 |
+
|
| 109 |
+
with solara.Row():
|
| 110 |
+
with solara.Columns([50,50]):
|
| 111 |
+
with solara.Column():
|
| 112 |
+
solara.SelectMultiple(label='Input cols', all_values=list(df.columns),
|
| 113 |
+
values=local_state.value['input_cols'].value,
|
| 114 |
+
on_value=select_input_cols)
|
| 115 |
+
solara.SelectMultiple(label='Output cols', all_values=list(df.columns),
|
| 116 |
+
values=local_state.value['output_cols'].value,
|
| 117 |
+
on_value=select_output_cols)
|
| 118 |
+
solara.Select(label="Optimizer", values=["Adam"],
|
| 119 |
+
value=local_state.value['optimizer_name'].value,
|
| 120 |
+
on_value=local_state.value['optimizer_name'].set)
|
| 121 |
+
|
| 122 |
+
solara.Select(label="Model", values=["Perceptron"],
|
| 123 |
+
value=local_state.value['model_name'].value,
|
| 124 |
+
on_value=local_state.value['model_name'].set)
|
| 125 |
+
|
| 126 |
+
solara.Select(label="Loss", values=['mape'],
|
| 127 |
+
value=local_state.value['loss_name'].value,
|
| 128 |
+
on_value=local_state.value['loss_name'].set)
|
| 129 |
+
|
| 130 |
+
with solara.Column():
|
| 131 |
+
solara.SliderFloat(label='Training ratio',
|
| 132 |
+
value=local_state.value['trn_ratio'].value, min=0, max=1,
|
| 133 |
+
on_value=local_state.value['trn_ratio'].set,
|
| 134 |
+
thumb_label=True)
|
| 135 |
+
solara.SliderInt(label='Batch size training',
|
| 136 |
+
value=local_state.value['batch_size_trn'].value, min=1, max=256,
|
| 137 |
+
on_value=local_state.value['batch_size_trn'].set,
|
| 138 |
+
thumb_label=True)
|
| 139 |
+
solara.SliderInt(label='Batch size validation',
|
| 140 |
+
value=local_state.value['batch_size_val'].value, min=1, max=256,
|
| 141 |
+
on_value=local_state.value['batch_size_val'].set,
|
| 142 |
+
thumb_label=True)
|
| 143 |
+
solara.SliderInt(label='Max epoch',
|
| 144 |
+
value=local_state.value['max_epoch'].value, min=1, max=1000,
|
| 145 |
+
on_value=local_state.value['max_epoch'].set,
|
| 146 |
+
thumb_label=True)
|
| 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 |
+
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
@solara.component
|
| 156 |
+
def Page():
|
| 157 |
+
df = state.value['data']
|
| 158 |
+
dff = df
|
| 159 |
+
filtered_cols = []
|
| 160 |
+
if len(local_state.value['input_cols'].value) > 0:
|
| 161 |
+
filtered_cols += local_state.value['input_cols'].value
|
| 162 |
+
if len(local_state.value['output_cols'].value) > 0:
|
| 163 |
+
filtered_cols += local_state.value['output_cols'].value
|
| 164 |
+
if len(filtered_cols) > 0:
|
| 165 |
+
dff = df[filtered_cols]
|
| 166 |
+
with solara.Columns([40,30,30]):
|
| 167 |
+
ParameterSelection(df)
|
| 168 |
+
FilterPanel(dff)
|
| 169 |
+
solara.CrossFilterDataFrame(dff, items_per_page=10)
|
| 170 |
+
ExecutePanel(dff)
|
| 171 |
+
|
| 172 |
+
|
agent/data/averaged_full_state_data.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
mypy.ini
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[mypy]
|
| 2 |
+
check_untyped_defs = True
|
| 3 |
+
ignore_missing_imports = True
|
pyproject.toml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["hatchling >=0.25"]
|
| 3 |
+
build-backend = "hatchling.build"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "agent"
|
| 7 |
+
license = {file = "LICENSE"}
|
| 8 |
+
classifiers = ["License :: OSI Approved :: MIT License"]
|
| 9 |
+
dynamic = ["version", "description"]
|
| 10 |
+
dependencies = [
|
| 11 |
+
"solara",
|
| 12 |
+
"pandas",
|
| 13 |
+
"torch",
|
| 14 |
+
"plotly",
|
| 15 |
+
"tqdm",
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
[tool.hatch.version]
|
| 19 |
+
path = "agent/__init__.py"
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
[project.urls]
|
| 24 |
+
Home = "https://www.github.com/app2scale/dashboard"
|
| 25 |
+
|
| 26 |
+
[tool.black]
|
| 27 |
+
line-length = 160
|
| 28 |
+
|
| 29 |
+
[tool.isort]
|
| 30 |
+
profile = "black"
|