Skip to content

Commit

Permalink
add error message,make logpath configurable, remove model_name requir…
Browse files Browse the repository at this point in the history
…ement
  • Loading branch information
Ceyda Cinarel committed Feb 16, 2021
1 parent 651f00b commit a494f5c
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 34 deletions.
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Torchserve Dashboard

Torchserve Dashboard using streamlit
Torchserve Dashboard using Streamlit

Related blog [post](https://cceyda.github.io/blog/torchserve/streamlit/dashboard/2020/10/15/torchserve.html)

Expand All @@ -13,9 +13,12 @@ Simply run:

```bash
pip3 install torchserve-dashboard --user
# torchserve-dashboard [streamlit_options] -- [config_path] [model_store(optional)]
# torchserve-dashboard [streamlit_options] -- [config_path] [model_store(optional)] [log_location(optional)] [metrics_location(optional)]
torchserve-dashboard --server.port 8105 -- --config_path ./torchserve.properties --model_store ./model_store
```

:exclamation: Keep in mind that If you change any of the `--config_path`,`--model_store`,`--metrics_location`,`--log_location` options while there is a torchserver already running before starting torch-dashboard they won't come into effect until you stop&start torchserve.

OR
```bash
git clone https://github.com/cceyda/torchserve-dashboard.git
Expand All @@ -31,10 +34,34 @@ number_of_gpu=0
batch_size=1
model_store=/mnt/pretrained/model_store
```

# Updates
[15-oct-2020] add [scale workers](https://pytorch.org/serve/management_api.html#scale-workers) tab

# Help
[16-feb-2021] (functionality) make logpath configurable,(functionality)remove model_name requirement,(UI)add cosmetic error messages

# FAQs
- **Does torchserver keep running in the background?**

The torchserver is spawned using `Popen` and keeps running in the background even if you stop the dashboard.

- **What about environment variables?**

These environment variables are passed to the torchserve command:

`ENVIRON_WHITELIST=["LD_LIBRARY_PATH","LC_CTYPE","LC_ALL","PATH","JAVA_HOME","PYTHONPATH","TS_CONFIG_FILE","LOG_LOCATION","METRICS_LOCATION"]`

- **How to change the logging format of torchserve?**

You can set the location of your custom log4j config in your configuration file as in [here](https://pytorch.org/serve/logging.html#provide-with-config-properties)

`vmargs=-Dlog4j.configuration=file:///path/to/custom/log4j.properties`

- **What is the meaning behind the weird versioning**

The minor follows the compatible torchserve version, patch version reflects the dashboard versioning

# Help & Question & Feedback

Open an issue

Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"""
from setuptools import find_packages, setup

dependencies = ["streamlit>=0.68", "click>=7.1.2", "httpx>=0.16.0"]
dependencies = ["streamlit>=0.76", "click>=7.1.2", "httpx>=0.16.0"]

setup(
name="torchserve_dashboard",
version="v0.2.3",
version="v0.2.4",
url="https://github.com/cceyda/torchserve-dashboard",
license="Apache Software License 2.0",
author="Ceyda Cinarel",
Expand Down
16 changes: 14 additions & 2 deletions torchserve_dashboard/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import streamlit as st

ENVIRON_WHITELIST=["LD_LIBRARY_PATH","LC_CTYPE","LC_ALL","PATH","JAVA_HOME","PYTHONPATH","TS_CONFIG_FILE","LOG_LOCATION","METRICS_LOCATION"]

def raise_on_not200(response):
if response.status_code != 200:
Expand All @@ -15,12 +16,23 @@ def raise_on_not200(response):
client = httpx.Client(timeout=1000, event_hooks={"response": [raise_on_not200]})


def start_torchserve(model_store, config_path):
def start_torchserve(model_store, config_path, log_location=None, metrics_location=None):
new_env={}
env=os.environ()
for x in ENVIRON_WHITELIST:
if x in env:
new_env[x]=env[x]

if log_location:
new_env["LOG_LOCATION"]=log_location
if metrics_location:
new_env["METRICS_LOCATION"]=metrics_location
if os.path.exists(model_store) and os.path.exists(config_path):
torchserve_cmd = f"torchserve --start --ncs --model-store {model_store} --ts-config {config_path}"
subprocess.Popen(
torchserve_cmd.split(" "),
stdout=open("/dev/null", "w"),
env=new_env,
stdout=open("/dev/null", "r"),
stderr=open("/dev/null", "w"),
preexec_fn=os.setpgrp,
)
Expand Down
73 changes: 46 additions & 27 deletions torchserve_dashboard/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@
from streamlit.script_runner import RerunException

import api as tsa
from pathlib import Path

st.set_page_config(
page_title="Torchserve Management Dashboard",
page_icon="./icon.png",
layout="centered",
initial_sidebar_state="expanded",
)

st.write(os.environ)
parser = argparse.ArgumentParser(description="Torchserve dashboard")

parser.add_argument("--model_store", default=None, help="Directory where your models are stored")
parser.add_argument("--config_path", default="./default.torchserve.properties", help="Torchserve config path")
parser.add_argument("--log_location", default="./", help="Passed as environment variable LOG_LOCATION to Torchserve")
parser.add_argument("--metrics_location", default="./", help="Passed as environment variable METRICS_LOCATION to Torchserve")
try:
args = parser.parse_args()
except SystemExit as e:
Expand All @@ -28,6 +31,12 @@
M_API = "http://127.0.0.1:8081"
model_store = args.model_store
config_path = args.config_path
log_location = args.log_location
if log_location:
log_location = str(Path(log_location).resolve())
metrics_location = args.metrics_location
if metrics_location:
metrics_location = str(Path(metrics_location).resolve())
config = None
default_key = "None"

Expand Down Expand Up @@ -56,16 +65,18 @@ def last_res():
def get_model_store():
return os.listdir(model_store)


# As a design choice I'm leaving config_path,log_location,metrics_location non-editable from the UI as a semi-security measure (maybe?:/)
##########Sidebar##########
st.sidebar.markdown(f"## Help")
st.sidebar.markdown(f"### Management API: \n {M_API}")
st.sidebar.markdown(f"### Model Store Path: \n {model_store}")
st.sidebar.markdown(f"### Config Path: \n {config_path}")
with st.sidebar.beta_expander(label="Show Paths:", expanded=False):
st.markdown(f"### Model Store Path: \n {model_store}")
st.markdown(f"### Config Path: \n {config_path}")
st.markdown(f"### Log Location: \n {log_location}")
st.markdown(f"### Metrics Location: \n {metrics_location}")

start = st.sidebar.button("Start Torchserve")
if start:
last_res()[0]= tsa.start_torchserve(model_store, config_path)
last_res()[0]= tsa.start_torchserve(model_store, config_path, log_location, metrics_location)
rerun()

stop = st.sidebar.button("Stop Torchserve")
Expand Down Expand Up @@ -104,7 +115,7 @@ def get_model_store():
p = st.checkbox("or use another path")
if p:
mar_path = placeholder.text_input("Input mar file path*")
model_name = st.text_input(label="Model name *")
model_name = st.text_input(label="Model name (overrides predefined)")
col1, col2 = st.beta_columns(2)
batch_size = col1.number_input(label="batch_size", value=0, min_value=0, step=1)
max_batch_delay = col2.number_input(label="max_batch_delay", value=0, min_value=0, step=100)
Expand All @@ -114,21 +125,26 @@ def get_model_store():
runtime = col2.text_input(label="runtime")

proceed = st.button("Register")
if proceed and model_name and mar_path != default_key:
st.write(f"Registering Model...{mar_path} as {model_name}")
res = tsa.register_model(
M_API,
mar_path,
model_name,
handler=handler,
runtime=runtime,
batch_size=batch_size,
max_batch_delay=max_batch_delay,
initial_workers=initial_workers,
response_timeout=response_timeout,
)
last_res()[0] = res
rerun()
if proceed:
if mar_path != default_key:
st.write(f"Registering Model...{mar_path}")
res = tsa.register_model(
M_API,
mar_path,
model_name,
handler=handler,
runtime=runtime,
batch_size=batch_size,
max_batch_delay=max_batch_delay,
initial_workers=initial_workers,
response_timeout=response_timeout,
)
last_res()[0] = res
rerun()
else:
st.write(":octagonal_sign: Fill the required fileds!")



with st.beta_expander(label="Remove a model", expanded=False):

Expand All @@ -141,11 +157,14 @@ def get_model_store():
versions = [m["modelVersion"] for m in versions]
version = st.selectbox("Choose version to remove", [default_key] + versions, index=0)
proceed = st.button("Remove")
if proceed and model_name != default_key and version != default_key:
res = tsa.delete_model(M_API, model_name, version)
last_res()[0] = res
rerun()

if proceed:
if model_name != default_key and version != default_key:
res = tsa.delete_model(M_API, model_name, version)
last_res()[0] = res
rerun()
else:
st.write(":octagonal_sign: Pick a model & version!")

with st.beta_expander(label="Get model details", expanded=False):

st.header("Get model details")
Expand Down

0 comments on commit a494f5c

Please sign in to comment.