Source code for ersilia.hub.fetch.pack.bentoml_pack.mode
import json
import os
from ..... import ErsiliaBase
from .....default import MODEL_CONFIG_FILENAME
from .....setup.requirements.conda import CondaRequirement
from .....setup.requirements.docker import DockerRequirement
from .....utils.system import SystemChecker
from .....utils.versioning import Versioner
from ....bundle.repo import DockerfileFile
AVAILABLE_MODES = ["system", "venv", "conda", "docker"]
[docs]
class PackModeDecision(ErsiliaBase):
"""
A class used to decide the packaging mode for a model.
Attributes
----------
model_id : str
ID of the model.
config_json : dict
Configuration settings in JSON format.
versioner : Versioner
Instance of Versioner for version checking.
Methods
-------
decide_from_config_file_if_available()
Decide the packaging mode from the config file if available.
decide()
Decide the packaging mode based on system and model requirements.
"""
def __init__(self, model_id: str, config_json: dict):
ErsiliaBase.__init__(self, config_json=config_json, credentials_json=None)
self.model_id = model_id
self.versioner = Versioner(config_json=config_json)
def _correct_protobuf(
self,
version: dict,
dockerfile: DockerfileFile,
protobuf_version: str = "3.19.5",
) -> DockerfileFile:
if version["version"] == "0.11.0":
self.logger.debug(
"Custom Ersilia BentoML is used, no need for modifying protobuf version"
)
return dockerfile
if "0.11" in version["version"]:
dockerfile.append_run_command(
"pip install protobuf=={0}".format(protobuf_version)
)
self.logger.info(
"Since BentoML is version 0.11, protobuf will been downgraded to {0}".format(
protobuf_version
)
)
return dockerfile
[docs]
def decide_from_config_file_if_available(self) -> str:
"""
Decide the packaging mode from the config file if available.
Returns
-------
str
Packaging mode if specified in the config file, None otherwise.
"""
folder = self._model_path(self.model_id)
if not os.path.exists(os.path.join(folder, MODEL_CONFIG_FILENAME)):
return None
with open(os.path.join(folder, MODEL_CONFIG_FILENAME), "r") as f:
model_config = json.load(f)
if "default_mode" in model_config:
default_mode = model_config["default_mode"]
if default_mode not in AVAILABLE_MODES:
raise Exception(
"The model default_mode specified in the config.json file of the model repo is not correct. It should be one of {0}".format(
" ".join(AVAILABLE_MODES)
)
)
else:
return default_mode
return None
[docs]
def decide(self) -> str:
"""
Decide the packaging mode based on system and model requirements.
Returns
-------
str
Decided packaging mode.
"""
sc = SystemChecker()
if sc.is_github_action():
self.logger.debug(
"Code is being run inside a GitHub Actions workflow. Use conda as a by-default mode."
)
return "conda"
mode = self.decide_from_config_file_if_available()
if mode is not None:
self.logger.debug("Mode is already specified in the model repository")
self.logger.debug("Mode: {0}".format(mode))
return mode
folder = self._model_path(self.model_id)
self.logger.debug(
"Check if model can be run with vanilla (system) code (i.e. dockerfile has no installs)"
)
dockerfile = DockerfileFile(folder)
self.logger.debug("Check bentoml and python version")
version = dockerfile.get_bentoml_version()
self.logger.info("BentoML version {0}".format(version))
dockerfile = self._correct_protobuf(version, dockerfile)
if not dockerfile.has_runs():
same_python = version["python"] == self.versioner.python_version(
py_format=True
)
same_bentoml = version["version"] == self.versioner.bentoml_version()
if same_python and same_bentoml:
self.logger.debug("Same python and same bentoml, run in system")
self.logger.debug("Mode: system")
return "system"
self.logger.debug("Model needs some installs")
cmds = dockerfile.get_install_commands()
if cmds is None:
self.logger.debug("No Dockerfile found...")
raise Exception("No Dockerfile found!")
self.logger.debug("Checking if only python/conda install will be sufficient")
if cmds["exclusive_conda_and_pip"]:
condareq = CondaRequirement()
if not cmds["conda"] and not condareq.is_installed():
self.logger.debug("Mode: venv")
return "venv"
else:
self.logger.debug("Mode: conda")
return "conda"
else:
self.logger.debug(
"The python/conda installs may not be sufficient, trying docker"
)
self.logger.debug("Mode: docker")
dockerreq = DockerRequirement()
if dockerreq.is_inside_docker():
return "conda"
if dockerreq.is_installed():
return "docker"
else:
return "conda"