Skip to content

Commit

Permalink
Performace Improvement on the run CLI(#1299) (#1353)
Browse files Browse the repository at this point in the history
* Update config.yml - fix Circle CI pipeline

* Performace Improvement on the run CLI(#1299)

* Fixing Error: Path error on model fetching

* Fixing Test Error: Awaiting the fetch function

* Fixing Test Error: Changing model id on model fetching

* Fixing Test Error: Updating compound.py code

* Update compound.py

* Update fetch.py

* Update fetch.py

* Performace Improvement on the run CLI(#1299)

---------

Co-authored-by: Dhanshree Arora <[email protected]>
  • Loading branch information
Abellegese and DhanshreeA authored Oct 31, 2024
1 parent 6677a22 commit ad6ce02
Show file tree
Hide file tree
Showing 12 changed files with 469 additions and 198 deletions.
5 changes: 4 additions & 1 deletion ersilia/cli/commands/fetch.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import click
import asyncio
import nest_asyncio
from . import ersilia_cli
from .. import echo
from ...hub.fetch.fetch import ModelFetcher
from ... import ModelBase

nest_asyncio.apply()

def fetch_cmd():
"""Create fetch commmand"""

def _fetch(mf, model_id):
mf.fetch(model_id)
asyncio.run(mf.fetch(model_id))

# Example usage: ersilia fetch {MODEL}
@ersilia_cli.command(
Expand Down
2 changes: 1 addition & 1 deletion ersilia/cli/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def run_cmd():
@click.option(
"--standard",
is_flag=True,
default=False,
default=True,
help="Assume that the run is standard and, therefore, do not do so many checks.",
)
def run(input, output, batch_size, standard):
Expand Down
2 changes: 1 addition & 1 deletion ersilia/core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ def _standard_api_runner(self, input, output):
"Standard CSV Api runner is not ready for this particular model"
)
return None
if not scra.is_amenable(input, output):
if not scra.is_amenable(output):
self.logger.debug(
"Standard CSV Api runner is not amenable for this model, input and output"
)
Expand Down
27 changes: 17 additions & 10 deletions ersilia/hub/fetch/fetch.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
"""Fetch Model from the Ersilia Model Hub."""

import os
import json
import importlib
Expand All @@ -13,6 +11,7 @@
NotInstallableWithFastAPI,
NotInstallableWithBentoML,
)
from .register.standard_example import ModelStandardExample
from ...utils.exceptions_utils.throw_ersilia_exception import throw_ersilia_exception
from ...default import PACK_METHOD_BENTOML, PACK_METHOD_FASTAPI, EOS, MODEL_SOURCE_FILE
from . import STATUS_FILE, DONE_TAG
Expand Down Expand Up @@ -156,9 +155,13 @@ def _fetch_not_from_dockerhub(self, model_id):
else:
self.logger.debug("Model already exists in your local, skipping fetching")

def _fetch_from_dockerhub(self, model_id):
def _standard_csv_example(self, model_id):
ms = ModelStandardExample(model_id=model_id, config_json=self.config_json)
ms.run()

async def _fetch_from_dockerhub(self, model_id):
self.logger.debug("Fetching from DockerHub")
self.model_dockerhub_fetcher.fetch(model_id=model_id)
await self.model_dockerhub_fetcher.fetch(model_id=model_id)

def _fetch_from_hosted(self, model_id):
self.logger.debug("Fetching from hosted")
Expand Down Expand Up @@ -213,14 +216,14 @@ def exists(self, model_id):
return True
else:
return False
def _fetch(self, model_id):

async def _fetch(self, model_id):

self.logger.debug("Starting fetching procedure")
do_dockerhub = self._decide_if_use_dockerhub(model_id=model_id)
if do_dockerhub:
self.logger.debug("Decided to fetch from DockerHub")
self._fetch_from_dockerhub(model_id=model_id)
await self._fetch_from_dockerhub(model_id=model_id)
return
do_hosted = self._decide_if_use_hosted(model_id=model_id)
if do_hosted:
Expand All @@ -233,10 +236,14 @@ def _fetch(self, model_id):
self.logger.debug("Fetching in your system, not from DockerHub")
self._fetch_not_from_dockerhub(model_id=model_id)

def fetch(self, model_id):
self._fetch(model_id)
async def fetch(self, model_id):
await self._fetch(model_id)
self._standard_csv_example(model_id)
self.logger.debug("Writing model source to file")
model_source_file = os.path.join(self._model_path(model_id), MODEL_SOURCE_FILE)
try:
os.makedirs(self._model_path(model_id), exist_ok=True)
except OSError as error:
self.logger.error(f"Error during folder creation: {error}")
with open(model_source_file, "w") as f:
f.write(self.model_source)

95 changes: 51 additions & 44 deletions ersilia/hub/fetch/lazy_fetchers/dockerhub.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import json
import asyncio
from ..register.register import ModelRegisterer

from .... import ErsiliaBase, throw_ersilia_exception
Expand All @@ -16,13 +17,11 @@
from ....serve.services import PulledDockerImageService
from ....setup.requirements.docker import DockerRequirement
from ....utils.docker import SimpleDocker, resolve_pack_method_docker, PACK_METHOD_BENTOML
from ....utils.exceptions_utils.fetch_exceptions import DockerNotActiveError
from .. import STATUS_FILE


class ModelDockerHubFetcher(ErsiliaBase):
def __init__(self, overwrite=None, config_json=None):
ErsiliaBase.__init__(self, config_json=config_json, credentials_json=None)
super().__init__(config_json=config_json, credentials_json=None)
self.simple_docker = SimpleDocker()
self.overwrite = overwrite

Expand All @@ -42,60 +41,63 @@ def is_available(self, model_id):
return True
return False

def write_apis(self, model_id):
async def write_apis(self, model_id):
self.logger.debug("Writing APIs")
di = PulledDockerImageService(
model_id=model_id, config_json=self.config_json, preferred_port=None
)
di.serve()
di.close()

def _copy_from_bentoml_image(self, model_id, file):
fr_file = "/root/eos/dest/{0}/{1}".format(model_id, file)
to_file = "{0}/dest/{1}/{2}".format(EOS, model_id, file)
self.simple_docker.cp_from_image(
img_path=fr_file,
local_path=to_file,
org=DOCKERHUB_ORG,
img=model_id,
tag=DOCKERHUB_LATEST_TAG,
)

def _copy_from_ersiliapack_image(self, model_id, file):
fr_file = "/root/{0}".format(file)
to_file = "{0}/dest/{1}/{2}".format(EOS, model_id, file)
self.simple_docker.cp_from_image(
async def _copy_from_bentoml_image(self, model_id, file):
fr_file = f"/root/eos/dest/{model_id}/{file}"
to_file = f"{EOS}/dest/{model_id}/{file}"
try:
await self.simple_docker.cp_from_image(
img_path=fr_file,
local_path=to_file,
org=DOCKERHUB_ORG,
img=model_id,
tag=DOCKERHUB_LATEST_TAG,
)
except Exception as e:
self.logger.error(f"Exception when copying: {e}")

async def _copy_from_ersiliapack_image(self, model_id, file):
fr_file = f"/root/{file}"
to_file = f"{EOS}/dest/{model_id}/{file}"
await self.simple_docker.cp_from_image(
img_path=fr_file,
local_path=to_file,
org=DOCKERHUB_ORG,
img=model_id,
tag=DOCKERHUB_LATEST_TAG,
)

def _copy_from_image_to_local(self, model_id, file):
async def _copy_from_image_to_local(self, model_id, file):
pack_method = resolve_pack_method_docker(model_id)
if pack_method == PACK_METHOD_BENTOML:
self._copy_from_bentoml_image(model_id, file)
await self._copy_from_bentoml_image(model_id, file)
else:
self._copy_from_ersiliapack_image(model_id, file)
await self._copy_from_ersiliapack_image(model_id, file)

def copy_information(self, model_id):
async def copy_information(self, model_id):
self.logger.debug("Copying information file from model container")
self._copy_from_image_to_local(model_id, INFORMATION_FILE)
await self._copy_from_image_to_local(model_id, INFORMATION_FILE)

def copy_metadata(self, model_id):
async def copy_metadata(self, model_id):
self.logger.debug("Copying api_schema_file file from model container")
self._copy_from_image_to_local(model_id, API_SCHEMA_FILE)
await self._copy_from_image_to_local(model_id, API_SCHEMA_FILE)

def copy_status(self, model_id):
async def copy_status(self, model_id):
self.logger.debug("Copying status file from model container")
self._copy_from_image_to_local(model_id, STATUS_FILE)
def copy_example_if_available(self, model_id):
# TODO This also needs to change to accomodate ersilia pack
await self._copy_from_image_to_local(model_id, STATUS_FILE)

async def copy_example_if_available(self, model_id):
# This needs to accommodate ersilia pack
for pf in PREDEFINED_EXAMPLE_FILES:
fr_file = "/root/eos/dest/{0}/{1}".format(model_id, pf)
to_file = "{0}/dest/{1}/{2}".format(EOS, model_id, "input.csv")
fr_file = f"/root/eos/dest/{model_id}/{pf}"
to_file = f"{EOS}/dest/{model_id}/input.csv"
try:
self.simple_docker.cp_from_image(
img_path=fr_file,
Expand All @@ -108,7 +110,7 @@ def copy_example_if_available(self, model_id):
except:
self.logger.debug("Could not find example file in docker image")

def modify_information(self, model_id):
async def modify_information(self, model_id):
"""
Modify the information file being copied from docker container to the host machine.
:param file: The model information file being copied.
Expand All @@ -124,7 +126,7 @@ def modify_information(self, model_id):
self.logger.error("Information file not found, not modifying anything")
return None

# Using this literal here to prevent a file read
# Using this literal here to prevent a file read
# from service class file for a model fetched through DockerHub
# since we already know the service class.
data["service_class"] = "pulled_docker"
Expand All @@ -133,15 +135,20 @@ def modify_information(self, model_id):
json.dump(data, outfile, indent=4)

@throw_ersilia_exception
def fetch(self, model_id):
async def fetch(self, model_id):
mp = ModelPuller(model_id=model_id, config_json=self.config_json)
self.logger.debug("Pulling model image from DockerHub")
mp.pull()
# Asynchronous pulling
await mp.async_pull()
mr = ModelRegisterer(model_id=model_id, config_json=self.config_json)
mr.register(is_from_dockerhub=True)
self.write_apis(model_id)
self.copy_information(model_id)
self.modify_information(model_id)
self.copy_metadata(model_id)
self.copy_status(model_id)
self.copy_example_if_available(model_id)
# Asynchronous and concurent execution
self.logger.debug("Asynchronous and concurrent execution started!")
await asyncio.gather(
mr.register(is_from_dockerhub=True),
self.write_apis(model_id),
self.copy_information(model_id),
self.modify_information(model_id),
self.copy_metadata(model_id),
self.copy_status(model_id),
self.copy_example_if_available(model_id)
)
2 changes: 1 addition & 1 deletion ersilia/hub/fetch/register/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def register_not_from_hosted(self):
with open(file_name, "w") as f:
json.dump(data, f)

def register(self, is_from_dockerhub=False, is_from_hosted=False):
async def register(self, is_from_dockerhub=False, is_from_hosted=False):
if is_from_dockerhub and is_from_hosted:
raise Exception
if is_from_dockerhub and not is_from_hosted:
Expand Down
Loading

0 comments on commit ad6ce02

Please sign in to comment.