From 7e335a148ebc8312af308acefa52eea4de5ce29b Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 01:07:17 +0530 Subject: [PATCH 01/43] add chalice class --- chitra/serve/cloud/__init__.py | 0 chitra/serve/cloud/aws_serverless.py | 67 ++++++++++++++++++++++++++++ chitra/serve/model_server.py | 4 ++ 3 files changed, 71 insertions(+) create mode 100644 chitra/serve/cloud/__init__.py create mode 100644 chitra/serve/cloud/aws_serverless.py diff --git a/chitra/serve/cloud/__init__.py b/chitra/serve/cloud/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/chitra/serve/cloud/aws_serverless.py b/chitra/serve/cloud/aws_serverless.py new file mode 100644 index 00000000..b72c3836 --- /dev/null +++ b/chitra/serve/cloud/aws_serverless.py @@ -0,0 +1,67 @@ +from typing import Callable + +import requests +from chalice import Chalice, Rate + +from chitra.serve.model_server import ModelServer + +S3 = "s3" +GCS = "gcs" + +RATE_UNIT = {"m": Rate.MINUTES, "h": Rate.HOURS, "d": Rate.DAYS} + + +def infer_location_type(path: str): + if path.startswith("s3"): + return S3 + elif path.startswith("gcs"): + return GCS + else: + raise ValueError(f"Location type is not supported yet for path={path}") + + +def download_model(path: str): + return requests.get(path, stream=True).raw + + +class ChaliceServer(ModelServer): + INVOKE_METHODS = ("route", "schedule", "on_s3_event") + + def __init__( + self, + api_type: str, + model_path: str, + model_loader: Callable, + preprocess_fn: Callable = None, + postprocess_fn: Callable = None, + **kwargs, + ): + infer_location_type(model_path) + model: Callable = model_loader(download_model(model_path)) + super().__init__(api_type, model, preprocess_fn, postprocess_fn, **kwargs) + + self.app = Chalice(app_name=kwargs.get("name", "chitra-server")) + + def predict(self, x) -> dict: + data_processor = self.data_processor + + if data_processor.preprocess_fn: + x = data_processor.preprocess(x, **self.preprocess_conf) + x = self.model(x) + if data_processor.postprocess_fn: + x = data_processor.postprocess(x, **self.postprocess_conf) + return x + + def run(self, invoke_method: str, **kwargs): + invoke_method = invoke_method.lower() + if invoke_method not in self.INVOKE_METHODS: + raise NotImplementedError( + f"invoke method={invoke_method} not implemented yet. Please select {self.INVOKE_METHODS}" + ) + + if invoke_method == "route": + route_path = kwargs.get("path", "/predict") + self.app.route(route_path, methods=["GET"])(self.predict) + + else: + raise NotImplementedError() diff --git a/chitra/serve/model_server.py b/chitra/serve/model_server.py index c68247ac..e9de9587 100644 --- a/chitra/serve/model_server.py +++ b/chitra/serve/model_server.py @@ -21,10 +21,14 @@ def __init__( model: Callable, preprocess_fn=None, postprocess_fn=None, + preprocess_conf: Optional[dict] = None, + postprocess_conf: Optional[dict] = None, **kwargs, ): self.api_type = api_type.upper() self.model = model + self.preprocess_conf = preprocess_conf + self.postprocess_conf = postprocess_conf self.data_processor: Optional[DataProcessor] = self.set_data_processor( preprocess_fn, postprocess_fn ) From 6585e2453c8b808599dc7045b91ec9a55fe6cc36 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 01:09:12 +0530 Subject: [PATCH 02/43] update --- chitra/serve/cloud/aws_serverless.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/chitra/serve/cloud/aws_serverless.py b/chitra/serve/cloud/aws_serverless.py index b72c3836..7d2e3b1e 100644 --- a/chitra/serve/cloud/aws_serverless.py +++ b/chitra/serve/cloud/aws_serverless.py @@ -14,10 +14,9 @@ def infer_location_type(path: str): if path.startswith("s3"): return S3 - elif path.startswith("gcs"): + if path.startswith("gcs"): return GCS - else: - raise ValueError(f"Location type is not supported yet for path={path}") + raise ValueError(f"Location type is not supported yet for path={path}") def download_model(path: str): From 5bd3a207c205dee9652910cd4ffae1106640216f Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 01:16:01 +0530 Subject: [PATCH 03/43] fix text_clf function --- chitra/serve/app.py | 10 +++++++--- tests/serve/test_app.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/chitra/serve/app.py b/chitra/serve/app.py index 1ad289d7..6625c49d 100644 --- a/chitra/serve/app.py +++ b/chitra/serve/app.py @@ -47,8 +47,12 @@ def setup( self, **kwargs, ): - - self.api_type_func[const.IMAGE_CLF] = self.image_classification + if self.api_type in (const.IMAGE_CLF, const.OBJECT_DETECTION): + self.api_type_func[self.api_type] = self.single_x_classification + elif self.api_type == const.TXT_CLF: + self.api_type_func[self.api_type] = self.single_x_classification + else: + raise NotImplementedError(f"api_type={self.api_type} not implemented yet!") if not self.input_types: self.input_types = self.get_input_type(**kwargs) @@ -67,7 +71,7 @@ def get_input_type(self, **kwargs): ) raise NotImplementedError(f"{self.api_type} API Type is not implemented yet!") - def image_classification(self, x: np.ndarray): + def single_x_classification(self, x: np.ndarray): data_processor = self.data_processor if data_processor.preprocess_fn: diff --git a/tests/serve/test_app.py b/tests/serve/test_app.py index 68449586..dfaa8f7f 100644 --- a/tests/serve/test_app.py +++ b/tests/serve/test_app.py @@ -54,7 +54,7 @@ def test_image_classification(): postprocess_conf=postprocess_conf, ) - assert app.image_classification(dummy_image) in (0, 1) + assert app.single_x_classification(dummy_image) in (0, 1) @pytest.mark.parametrize( From 0c4f0daa18f35cce19de3e5cf2c9104495ecf6c1 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 01:18:53 +0530 Subject: [PATCH 04/43] update gh-label --- .github/labeler.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index 0acee015..e1e42968 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -7,3 +7,9 @@ example: test: - tests/**/* + +serve: +- chitra/serve/**/* + +cli: +- chitra/cli/**/* From 1a86ffd7a27dc634a7cd2b4535a4a8ad2faa4f7c Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 08:17:59 +0530 Subject: [PATCH 05/43] update gh-label --- .github/labeler.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index e1e42968..7759ca37 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,15 +1,16 @@ # Add 'docs' to any changes within 'docs' folder or any subfolders +# https://stackoverflow.com/questions/34691809/regex-match-folder-and-all-subfolders documentation: - docs/**/* example: -- example/**/* +- examples($|/.*) test: - tests/**/* serve: -- chitra/serve/**/* +- chitra/serve($|/.*) cli: -- chitra/cli/**/* +- chitra/cli($|/.*) From 499a373e5f6631a09166cfe428a33c0e9a09a0fc Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 08:20:46 +0530 Subject: [PATCH 06/43] rename model_server to base --- chitra/serve/__init__.py | 2 +- chitra/serve/api.py | 2 +- chitra/serve/app.py | 2 +- chitra/serve/{model_server.py => base.py} | 0 chitra/serve/cloud/aws_serverless.py | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename chitra/serve/{model_server.py => base.py} (100%) diff --git a/chitra/serve/__init__.py b/chitra/serve/__init__.py index 89650400..4286a0c8 100644 --- a/chitra/serve/__init__.py +++ b/chitra/serve/__init__.py @@ -1,3 +1,3 @@ from chitra.serve.api import API, create_api from chitra.serve.app import GradioApp -from chitra.serve.model_server import ModelServer +from chitra.serve.base import ModelServer diff --git a/chitra/serve/api.py b/chitra/serve/api.py index 6422edf7..72f3dff0 100644 --- a/chitra/serve/api.py +++ b/chitra/serve/api.py @@ -5,8 +5,8 @@ from chitra.__about__ import documentation_url from chitra.serve import schema +from chitra.serve.base import ModelServer from chitra.serve.constants import IMAGE_CLF, OBJECT_DETECTION, QNA, TXT_CLF -from chitra.serve.model_server import ModelServer class API(ModelServer): diff --git a/chitra/serve/app.py b/chitra/serve/app.py index 6625c49d..07953189 100644 --- a/chitra/serve/app.py +++ b/chitra/serve/app.py @@ -5,7 +5,7 @@ from chitra.__about__ import documentation_url from chitra.serve import constants as const -from chitra.serve.model_server import ModelServer +from chitra.serve.base import ModelServer class GradioApp(ModelServer): diff --git a/chitra/serve/model_server.py b/chitra/serve/base.py similarity index 100% rename from chitra/serve/model_server.py rename to chitra/serve/base.py diff --git a/chitra/serve/cloud/aws_serverless.py b/chitra/serve/cloud/aws_serverless.py index 7d2e3b1e..b2c6227c 100644 --- a/chitra/serve/cloud/aws_serverless.py +++ b/chitra/serve/cloud/aws_serverless.py @@ -3,7 +3,7 @@ import requests from chalice import Chalice, Rate -from chitra.serve.model_server import ModelServer +from chitra.serve.base import ModelServer S3 = "s3" GCS = "gcs" From 57daf84948ae03f64a30085b107c14d0a8fe2305 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 08:22:58 +0530 Subject: [PATCH 07/43] build docs --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index b2e36eb0..b2ff97a0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -42,7 +42,7 @@ Easily create UI for Machine Learning models or Rest API backend that can be dep ### Using pip (recommended) -`pip install -U chitra==0.1.0` +`pip install -U chitra` ### From source From cd8bb9d714d615fd561096d5f99cd41efe7b34ed Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 08:24:41 +0530 Subject: [PATCH 08/43] dist -> build --- Makefile | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index ee2f07b2..370eff81 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,4 @@ -SRC = $(wildcard ./*.ipynb) - -all: chitra docs +.PHONY: build_docs build build_docs: cp README.md docs/index.md @@ -29,10 +27,10 @@ style: black chitra tests examples isort chitra tests examples -dist: clean +build: clean flit build -pypi: dist +pypi: build flit publish push: From 4b99733e2eb7f6e2f3b85ee74c741c4787276b7c Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 08:43:32 +0530 Subject: [PATCH 09/43] add resize method --- Makefile | 4 ++-- chitra/image.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 370eff81..68d14993 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ coverage: ## Run tests with coverage coverage xml clean: - rm -rf dist + rm -rf dist/ find . -type f -name "*.DS_Store" -ls -delete find . | grep -E "(__pycache__|\.pyc|\.pyo)" | xargs rm -rf find . | grep -E ".pytest_cache" | xargs rm -rf @@ -27,7 +27,7 @@ style: black chitra tests examples isort chitra tests examples -build: clean +build: style clean flit build pypi: build diff --git a/chitra/image.py b/chitra/image.py index abc9dea5..eb281ec3 100644 --- a/chitra/image.py +++ b/chitra/image.py @@ -147,6 +147,19 @@ def draw_boxes( self.numpy()[..., :3], color=color, size=marker_size ) + def resize(self, *args, **kwargs) -> Image.Image: + """ + Calls PIL.Image.resize method and passes the arguments + Args: + *args: + **kwargs: + + Returns: + resized PIL.Image + """ + self.image = self.image.resize(*args, **kwargs) + return self.image + def resize_image_with_bbox(self, size: List[int]): old_size = self.shape self.image = self.image.resize(size) From 86e11b52266dba6f21a31683572318c876894784 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 08:55:11 +0530 Subject: [PATCH 10/43] update --- chitra/__init__.py | 2 +- chitra/serve/cloud/__init__.py | 1 + examples/nbs/playground.ipynb | 144 +++++++-------------------------- 3 files changed, 31 insertions(+), 116 deletions(-) diff --git a/chitra/__init__.py b/chitra/__init__.py index f561a888..526de7df 100644 --- a/chitra/__init__.py +++ b/chitra/__init__.py @@ -1,4 +1,4 @@ """Deep Learning library for Model Building, Interpretability, Visualization, API Building & Deployment.""" -__version__ = "0.1.1" +__version__ = "0.2.0a0" __license__ = "Apache License 2.0" diff --git a/chitra/serve/cloud/__init__.py b/chitra/serve/cloud/__init__.py index e69de29b..034a376a 100644 --- a/chitra/serve/cloud/__init__.py +++ b/chitra/serve/cloud/__init__.py @@ -0,0 +1 @@ +from .aws_serverless import ChaliceServer diff --git a/examples/nbs/playground.ipynb b/examples/nbs/playground.ipynb index b56e3770..fd713723 100644 --- a/examples/nbs/playground.ipynb +++ b/examples/nbs/playground.ipynb @@ -3,57 +3,60 @@ { "cell_type": "code", "execution_count": null, - "id": "24d6e10b-ebc0-485e-888b-203f1c0b5e27", + "id": "30596049-1933-41e9-b189-9ab00f918116", "metadata": {}, "outputs": [], "source": [ - "import sys" + "from chitra.serve.cloud.aws_serverless import ChaliceServer" ] }, { "cell_type": "code", "execution_count": null, - "id": "e67b47e3-3195-479f-8e18-8d9e0c3ef772", + "id": "67b705a4-6221-44b4-8638-c45a6c6281db", "metadata": {}, "outputs": [], "source": [ - "sys.path.append('../')" + "def load_model(file):\n", + " return (lambda x: x + 1)" ] }, { "cell_type": "code", "execution_count": null, - "id": "bfab49a8-55df-4212-a34f-3422619cf5c7", + "id": "5756afb5-963e-4f99-997b-263dd7795c8f", "metadata": {}, "outputs": [], "source": [ - "from chitra.serve import create_api" + "# server = ChaliceServer('image-classification',\n", + "# \"s3://anmfm\",\n", + "# model_loader=load_model)" ] }, { "cell_type": "code", "execution_count": null, - "id": "e0c57137-c3e3-4e49-8be4-8dd3919e85c7", + "id": "c760e259-82e9-4bfc-a2d8-7e4b14684bc1", "metadata": {}, "outputs": [], "source": [ - "model = lambda x: x+1" + "from timm import create_model, list_models" ] }, { "cell_type": "code", "execution_count": null, - "id": "6735c42d-5bbc-46a1-9048-a856ae6f7597", + "id": "0cd48a18-b989-4699-a080-efd64f38b363", "metadata": {}, "outputs": [], "source": [ - "create_api(model, run=True)" + "from chitra.image import Chitra" ] }, { "cell_type": "code", "execution_count": null, - "id": "9883733f-dd68-4f4c-b7b7-5696d5c18451", + "id": "c4582b8f-82e3-4330-8f2d-30da85f9d5aa", "metadata": {}, "outputs": [], "source": [] @@ -61,148 +64,59 @@ { "cell_type": "code", "execution_count": null, - "id": "8d8b37ad-45ae-4d02-87d8-8d9ed2a7ba5d", + "id": "8d60fd6c-0322-4399-900a-9b244d26fec1", "metadata": {}, "outputs": [], - "source": [ - "import tempfile" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2ec7209b-cba5-48ea-8993-8d5b31b53165", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import tempfile\n", - "from contextlib import contextmanager\n", - "\n", - "from typing import Optional" - ] + "source": [] }, { "cell_type": "code", "execution_count": null, - "id": "0b86260f-0aa5-4943-938a-44a0d25b7588", + "id": "c6feb93c-be27-403f-a1d4-4370ef0c7b58", "metadata": {}, "outputs": [], "source": [ - "def str_to_file(text: str):\n", - " temp = None\n", - " try:\n", - " temp = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False)\n", - " temp.write(text)\n", - " return temp\n", - " finally:\n", - " temp.close()" + "model = create_model('efficientnet_b0',\n", + " pretrained=True,\n", + " checkpoint_path='./efficientnet_b0_ra-3dd342df.pth')" ] }, { "cell_type": "code", "execution_count": null, - "id": "8b7239bc-c4f1-42f9-8678-0ecf9424b939", + "id": "3ea121da-f2df-47c0-a09a-3d474752d321", "metadata": {}, "outputs": [], "source": [ - "with str_to_file('rfa') as r:\n", - " print(r.name)\n", - " " + "model" ] }, { "cell_type": "code", "execution_count": null, - "id": "76483dfe-4e09-4b0d-b224-42c80abf751f", + "id": "1910c175-8fe1-4b97-868b-38bb927b58bc", "metadata": {}, "outputs": [], "source": [ - "import os\n", - "import tempfile\n", - "from typing import Optional\n", - "\n", - "COPY_PL = \"COPY_PL\"\n", - "PORT_PL = \"PORT_PL\"\n", - "\n", - "GUNICORN_DEFAULT_CONF = \"\"\"\n", - "import multiprocessing\n", - "\n", - "workers = multiprocessing.cpu_count()\n", - "\"\"\"\n", - "\n", - "DOCKER_FILE = f\"\"\"\n", - "FROM python:3.7\n", - "\n", - "LABEL maintainer=\"Aniket Maurya \"\n", - "\n", - "RUN pip install --no-cache-dir \"chitra[serve]\" gunicorn\n", - "\n", - "{COPY_PL}\n", - "\n", - "EXPOSE PORT_PL\n", - "\n", - "ENTRYPOINT gunicorn -c gunicorn_conf.py api:app\n", - "\n", - "\"\"\"\n", - "\n", - "\n", - "def str_to_file(text: str):\n", - " temp = tempfile.NamedTemporaryFile(mode=\"w+\", encoding=\"utf-8\", delete=False)\n", - " temp.write(text)\n", - " return temp\n", - "\n", - "\n", - "def dockerize_api(\n", - " source_path: str, gunicorn_conf_path: Optional[str] = None, port: str = \"8080\"\n", - "):\n", - " if gunicorn_conf_path is None:\n", - " gunicorn_conf_path = str_to_file(GUNICORN_DEFAULT_CONF).name\n", - "\n", - " if not os.path.exists(gunicorn_conf_path):\n", - " raise FileNotFoundError(\n", - " f\"gunicorn_conf_path not found at - {gunicorn_conf_path}\"\n", - " )\n", - "\n", - " if not os.path.exists(source_path):\n", - " raise FileNotFoundError(\n", - " \"You must provide the folder/file path of your Serving code\"\n", - " )\n", - "\n", - " copy_cmd = (\n", - " f\"COPY {source_path} ./ \\n\" + f\"COPY {gunicorn_conf_path} ./gunicorn_conf.py\"\n", - " )\n", - "\n", - " copy_cmd = copy_cmd.strip()\n", - " docker_cmd = DOCKER_FILE[:]\n", - " docker_cmd = docker_cmd.replace(COPY_PL, copy_cmd)\n", - " docker_cmd = docker_cmd.replace(PORT_PL, port)\n", - "\n", - " return docker_cmd\n" + "image = Chitra(\n", + " \"https://ichef.bbci.co.uk/news/976/cpsprodpb/67CF/production/_108857562_mediaitem108857561.jpg\"\n", + ")" ] }, { "cell_type": "code", "execution_count": null, - "id": "b7465841-f68c-40d3-b688-c3241704bd3b", + "id": "7af8a45e-384c-4f61-9d91-589620a55966", "metadata": {}, "outputs": [], "source": [ - "print(dockerize_api('./'))" + "image.image = image.image.resize((256, 256))" ] }, { "cell_type": "code", "execution_count": null, - "id": "c0e02c80-ff00-4c35-8bdf-4bb2a37a8dbf", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "745588c4-5e4c-40e9-bc2c-b515993d8bb8", + "id": "aedc7a9b-3b09-495d-b449-0fdc15204b94", "metadata": {}, "outputs": [], "source": [] From f6a7f6428262488fc1dce2a30e931f73cc165527 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 08:56:07 +0530 Subject: [PATCH 11/43] ignore models --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 89fcad55..be036164 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,7 @@ checklink/cookies.txt # .gitconfig is now autogenerated .gitconfig + +*.pth +*.ckpt +*.h5 From 01698b47cde59a5fb6e8849abcff2a79728513c9 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 10:05:37 +0530 Subject: [PATCH 12/43] refactor --- chitra/serve/cloud/aws_serverless.py | 34 ++++-- chitra/serve/cloud/base.py | 31 +++++ examples/nbs/playground.ipynb | 171 +++++++++++++++++++++++---- pyproject.toml | 1 + 4 files changed, 209 insertions(+), 28 deletions(-) create mode 100644 chitra/serve/cloud/base.py diff --git a/chitra/serve/cloud/aws_serverless.py b/chitra/serve/cloud/aws_serverless.py index b2c6227c..75a75a27 100644 --- a/chitra/serve/cloud/aws_serverless.py +++ b/chitra/serve/cloud/aws_serverless.py @@ -1,9 +1,11 @@ +import io from typing import Callable import requests +import smart_open from chalice import Chalice, Rate -from chitra.serve.base import ModelServer +from chitra.serve.cloud.base import CloudServer S3 = "s3" GCS = "gcs" @@ -19,11 +21,24 @@ def infer_location_type(path: str): raise ValueError(f"Location type is not supported yet for path={path}") -def download_model(path: str): - return requests.get(path, stream=True).raw +def download_model(path: str, **kwargs) -> io.BytesIO: + """ + Download model from cloud + ref: http://5.9.10.113/67706477/load-pytorch-model-from-s3-bucket + Args: + path: + **kwargs: + Returns: -class ChaliceServer(ModelServer): + """ + + with smart_open.open(path, mode="rb", **kwargs) as fr: + data = io.BytesIO(fr.read()) + return data + + +class ChaliceServer(CloudServer): INVOKE_METHODS = ("route", "schedule", "on_s3_event") def __init__( @@ -35,9 +50,14 @@ def __init__( postprocess_fn: Callable = None, **kwargs, ): - infer_location_type(model_path) - model: Callable = model_loader(download_model(model_path)) - super().__init__(api_type, model, preprocess_fn, postprocess_fn, **kwargs) + super().__init__( + api_type, + model_path=model_path, + model_loader=model_loader, + preprocess_fn=preprocess_fn, + postprocess_fn=postprocess_fn, + **kwargs, + ) self.app = Chalice(app_name=kwargs.get("name", "chitra-server")) diff --git a/chitra/serve/cloud/base.py b/chitra/serve/cloud/base.py new file mode 100644 index 00000000..e1f14ce0 --- /dev/null +++ b/chitra/serve/cloud/base.py @@ -0,0 +1,31 @@ +import io +from abc import ABC +from typing import Callable + +from chitra.serve.base import ModelServer + + +class CloudServer(ModelServer, ABC): + def __init__( + self, + api_type: str, + model_path: str, + model_loader: Callable, + preprocess_fn: Callable, + postprocess_fn: Callable, + **kwargs + ): + raw_model = self.download_model(model_path, **kwargs) + model = model_loader(raw_model) + + super().__init__( + api_type, + model, + preprocess_fn=preprocess_fn, + postprocess_fn=postprocess_fn, + **kwargs + ) + + def download_model(self, path: str, **kwargs) -> io.BytesIO: + """Download Model from cloud""" + raise NotImplementedError diff --git a/examples/nbs/playground.ipynb b/examples/nbs/playground.ipynb index fd713723..f21f993b 100644 --- a/examples/nbs/playground.ipynb +++ b/examples/nbs/playground.ipynb @@ -7,7 +7,14 @@ "metadata": {}, "outputs": [], "source": [ - "from chitra.serve.cloud.aws_serverless import ChaliceServer" + "import io\n", + "\n", + "import numpy as np\n", + "import torch\n", + "from chitra.core import load_imagenet_labels\n", + "from chitra.serve.cloud.aws_serverless import ChaliceServer\n", + "from smart_open import open as smart_open\n", + "from timm import create_model, list_models" ] }, { @@ -24,39 +31,52 @@ { "cell_type": "code", "execution_count": null, - "id": "5756afb5-963e-4f99-997b-263dd7795c8f", + "id": "ae2c7d06-e1ba-46e4-b666-568e92222b77", "metadata": {}, "outputs": [], "source": [ - "# server = ChaliceServer('image-classification',\n", - "# \"s3://anmfm\",\n", - "# model_loader=load_model)" + "import boto3\n", + "\n", + "s3 = boto3.client('s3')" ] }, { "cell_type": "code", "execution_count": null, - "id": "c760e259-82e9-4bfc-a2d8-7e4b14684bc1", + "id": "e4d05656-0721-4897-a517-e745588d0968", "metadata": {}, "outputs": [], "source": [ - "from timm import create_model, list_models" + "response = s3.list_buckets()" ] }, { "cell_type": "code", "execution_count": null, - "id": "0cd48a18-b989-4699-a080-efd64f38b363", + "id": "887d362e-38f6-4986-a30b-a5a275bae802", "metadata": {}, "outputs": [], "source": [ - "from chitra.image import Chitra" + "# Output the bucket names\n", + "print('Existing buckets:')\n", + "for bucket in response['Buckets']:\n", + " print(f' {bucket[\"Name\"]}')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d7dec4c1-7b3b-460a-bf01-ca553d6ba72d", + "metadata": {}, + "outputs": [], + "source": [ + "file = s3.download_file('gflow-models', 'efficientnet_b0.pth', 'model.pth')" ] }, { "cell_type": "code", "execution_count": null, - "id": "c4582b8f-82e3-4330-8f2d-30da85f9d5aa", + "id": "514769e7-8917-4909-957e-7ee7019d76be", "metadata": {}, "outputs": [], "source": [] @@ -64,7 +84,68 @@ { "cell_type": "code", "execution_count": null, - "id": "8d60fd6c-0322-4399-900a-9b244d26fec1", + "id": "4e088dc0-49a2-4081-91a2-6d41b45ed7b2", + "metadata": {}, + "outputs": [], + "source": [ + "BUCKET = 'gflow-models'\n", + "FILE_OBJ = 'efficientnet_b0.pth'\n", + "MODEL_PATH = \"s3://gflow-models/efficientnet_b0.pth\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a0505a45-0267-4264-9297-bc19812ef535", + "metadata": {}, + "outputs": [], + "source": [ + "s3 = boto3.resource('s3')\n", + "bucket = s3.Bucket(BUCKET)\n", + "file_object = bucket.Object(FILE_OBJ)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38fe81de-b312-4c52-b08c-2e1ac34b27be", + "metadata": {}, + "outputs": [], + "source": [ + "buffer = io.BytesIO()\n", + "file_object.download_fileobj(buffer)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7da088ff-7f63-4382-9efa-7952619908b8", + "metadata": {}, + "outputs": [], + "source": [ + "model = create_model('efficientnet_b0', pretrained=False)\n", + "\n", + "with smart_open(MODEL_PATH, 'rb') as f:\n", + " buffer = io.BytesIO(f.read())\n", + " model = model.load_state_dict(torch.load(buffer))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5756afb5-963e-4f99-997b-263dd7795c8f", + "metadata": {}, + "outputs": [], + "source": [ + "# server = ChaliceServer('image-classification',\n", + "# \"s3://gflow-models/efficientnet_b0.pth\",\n", + "# model_loader=load_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c760e259-82e9-4bfc-a2d8-7e4b14684bc1", "metadata": {}, "outputs": [], "source": [] @@ -72,25 +153,44 @@ { "cell_type": "code", "execution_count": null, - "id": "c6feb93c-be27-403f-a1d4-4370ef0c7b58", + "id": "0cd48a18-b989-4699-a080-efd64f38b363", "metadata": {}, "outputs": [], "source": [ - "model = create_model('efficientnet_b0',\n", - " pretrained=True,\n", - " checkpoint_path='./efficientnet_b0_ra-3dd342df.pth')" + "from chitra.image import Chitra" ] }, { "cell_type": "code", "execution_count": null, - "id": "3ea121da-f2df-47c0-a09a-3d474752d321", + "id": "f5a448e6-5ce4-45ad-a0bc-a87878900cb5", "metadata": {}, "outputs": [], "source": [ - "model" + "LABELS = load_imagenet_labels()" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6feb93c-be27-403f-a1d4-4370ef0c7b58", + "metadata": {}, + "outputs": [], + "source": [ + "model = create_model(\n", + " 'efficientnet_b0',\n", + " pretrained=True,\n", + " checkpoint_path='./efficientnet_b0_ra-3dd342df.pth').eval()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ea121da-f2df-47c0-a09a-3d474752d321", + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": null, @@ -100,23 +200,52 @@ "source": [ "image = Chitra(\n", " \"https://ichef.bbci.co.uk/news/976/cpsprodpb/67CF/production/_108857562_mediaitem108857561.jpg\"\n", - ")" + ")\n", + "image.resize((256, 256))" ] }, { "cell_type": "code", "execution_count": null, - "id": "7af8a45e-384c-4f61-9d91-589620a55966", + "id": "77eba50d-bb2a-47a1-96a9-4cbefd998b45", "metadata": {}, "outputs": [], "source": [ - "image.image = image.image.resize((256, 256))" + "x = image.numpy().astype(np.float32)\n", + "x = x / 255.\n", + "x = torch.from_numpy(x)\n", + "\n", + "LABELS[model(x.permute(2, 0, 1).unsqueeze(0)).argmax(1)]" ] }, { "cell_type": "code", "execution_count": null, - "id": "aedc7a9b-3b09-495d-b449-0fdc15204b94", + "id": "dcbb7d46-0c4f-4ada-9a80-65e3939950a8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8c3ada9-5e5a-4936-9407-5b7a88947ff8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6eee0989-da5a-448f-a6bd-5a325adde7fc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d044465c-f308-49b1-b827-33337b111182", "metadata": {}, "outputs": [], "source": [] diff --git a/pyproject.toml b/pyproject.toml index b667e276..0d496f1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ Documentation = "https://chitra.readthedocs.io/en/latest" [tool.flit.metadata.requires-extra] serve = [ "fastapi", "uvicorn", "pydantic", "python-multipart", "tensorflow-serving-api", + "chalice", "smart_open" ] converter = [ "onnx", "tf2onnx", From 5ee615bbc96f3a7248d375b4d91da378eaad156b Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 10:13:34 +0530 Subject: [PATCH 13/43] update --- chitra/serve/cloud/aws_serverless.py | 20 -------------------- chitra/serve/cloud/base.py | 18 ++++++++++++++++-- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/chitra/serve/cloud/aws_serverless.py b/chitra/serve/cloud/aws_serverless.py index 75a75a27..af4c104b 100644 --- a/chitra/serve/cloud/aws_serverless.py +++ b/chitra/serve/cloud/aws_serverless.py @@ -1,8 +1,5 @@ -import io from typing import Callable -import requests -import smart_open from chalice import Chalice, Rate from chitra.serve.cloud.base import CloudServer @@ -21,23 +18,6 @@ def infer_location_type(path: str): raise ValueError(f"Location type is not supported yet for path={path}") -def download_model(path: str, **kwargs) -> io.BytesIO: - """ - Download model from cloud - ref: http://5.9.10.113/67706477/load-pytorch-model-from-s3-bucket - Args: - path: - **kwargs: - - Returns: - - """ - - with smart_open.open(path, mode="rb", **kwargs) as fr: - data = io.BytesIO(fr.read()) - return data - - class ChaliceServer(CloudServer): INVOKE_METHODS = ("route", "schedule", "on_s3_event") diff --git a/chitra/serve/cloud/base.py b/chitra/serve/cloud/base.py index e1f14ce0..e27a6e8c 100644 --- a/chitra/serve/cloud/base.py +++ b/chitra/serve/cloud/base.py @@ -2,6 +2,8 @@ from abc import ABC from typing import Callable +import smart_open + from chitra.serve.base import ModelServer @@ -27,5 +29,17 @@ def __init__( ) def download_model(self, path: str, **kwargs) -> io.BytesIO: - """Download Model from cloud""" - raise NotImplementedError + """ + Download model from cloud + ref: http://5.9.10.113/67706477/load-pytorch-model-from-s3-bucket + Args: + path: + **kwargs: + + Returns: + + """ + + with smart_open.open(path, mode="rb", **kwargs) as fr: + data = io.BytesIO(fr.read()) + return data From 34e9877a6f904ff2b87989fdd5313e6ce688e435 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 10:30:22 +0530 Subject: [PATCH 14/43] add example --- chitra/serve/cloud/aws_serverless.py | 11 +-- chitra/serve/cloud/base.py | 3 + examples/nbs/playground.ipynb | 86 ++++------------------- examples/src/deployment/aws-lambda/app.py | 44 ++++++++++++ 4 files changed, 67 insertions(+), 77 deletions(-) create mode 100644 examples/src/deployment/aws-lambda/app.py diff --git a/chitra/serve/cloud/aws_serverless.py b/chitra/serve/cloud/aws_serverless.py index af4c104b..af906042 100644 --- a/chitra/serve/cloud/aws_serverless.py +++ b/chitra/serve/cloud/aws_serverless.py @@ -19,7 +19,7 @@ def infer_location_type(path: str): class ChaliceServer(CloudServer): - INVOKE_METHODS = ("route", "schedule", "on_s3_event") + INVOKE_METHODS = ("route",) def __init__( self, @@ -41,6 +41,9 @@ def __init__( self.app = Chalice(app_name=kwargs.get("name", "chitra-server")) + def index(self): + return {"hello": "world"} + def predict(self, x) -> dict: data_processor = self.data_processor @@ -60,7 +63,5 @@ def run(self, invoke_method: str, **kwargs): if invoke_method == "route": route_path = kwargs.get("path", "/predict") - self.app.route(route_path, methods=["GET"])(self.predict) - - else: - raise NotImplementedError() + self.app.route("/", methods=["GET"])(self.index) + self.app.route(route_path, methods=["POST"])(self.predict) diff --git a/chitra/serve/cloud/base.py b/chitra/serve/cloud/base.py index e27a6e8c..9c3a4621 100644 --- a/chitra/serve/cloud/base.py +++ b/chitra/serve/cloud/base.py @@ -43,3 +43,6 @@ def download_model(self, path: str, **kwargs) -> io.BytesIO: with smart_open.open(path, mode="rb", **kwargs) as fr: data = io.BytesIO(fr.read()) return data + + def run(self, *_, **__): + raise NotImplementedError diff --git a/examples/nbs/playground.ipynb b/examples/nbs/playground.ipynb index f21f993b..68bda52a 100644 --- a/examples/nbs/playground.ipynb +++ b/examples/nbs/playground.ipynb @@ -20,18 +20,7 @@ { "cell_type": "code", "execution_count": null, - "id": "67b705a4-6221-44b4-8638-c45a6c6281db", - "metadata": {}, - "outputs": [], - "source": [ - "def load_model(file):\n", - " return (lambda x: x + 1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ae2c7d06-e1ba-46e4-b666-568e92222b77", + "id": "25ce4031-9696-4f9e-bf81-7d1e8624e562", "metadata": {}, "outputs": [], "source": [ @@ -43,44 +32,16 @@ { "cell_type": "code", "execution_count": null, - "id": "e4d05656-0721-4897-a517-e745588d0968", - "metadata": {}, - "outputs": [], - "source": [ - "response = s3.list_buckets()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "887d362e-38f6-4986-a30b-a5a275bae802", - "metadata": {}, - "outputs": [], - "source": [ - "# Output the bucket names\n", - "print('Existing buckets:')\n", - "for bucket in response['Buckets']:\n", - " print(f' {bucket[\"Name\"]}')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d7dec4c1-7b3b-460a-bf01-ca553d6ba72d", + "id": "67b705a4-6221-44b4-8638-c45a6c6281db", "metadata": {}, "outputs": [], "source": [ - "file = s3.download_file('gflow-models', 'efficientnet_b0.pth', 'model.pth')" + "def model_loader(buffer: io.BytesIO)-> torch.nn.Module:\n", + " model: torch.nn.Module = create_model('efficientnet_b0', pretrained=False)\n", + " model.load_state_dict(torch.load(buffer))\n", + " return model" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "514769e7-8917-4909-957e-7ee7019d76be", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": null, @@ -96,51 +57,32 @@ { "cell_type": "code", "execution_count": null, - "id": "a0505a45-0267-4264-9297-bc19812ef535", - "metadata": {}, - "outputs": [], - "source": [ - "s3 = boto3.resource('s3')\n", - "bucket = s3.Bucket(BUCKET)\n", - "file_object = bucket.Object(FILE_OBJ)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "38fe81de-b312-4c52-b08c-2e1ac34b27be", + "id": "5756afb5-963e-4f99-997b-263dd7795c8f", "metadata": {}, "outputs": [], "source": [ - "buffer = io.BytesIO()\n", - "file_object.download_fileobj(buffer)" + "server = ChaliceServer('image-classification',\n", + " MODEL_PATH,\n", + " model_loader=model_loader)" ] }, { "cell_type": "code", "execution_count": null, - "id": "7da088ff-7f63-4382-9efa-7952619908b8", + "id": "8b3a86e5-1d2f-46c7-8e68-d7da76652ccc", "metadata": {}, "outputs": [], "source": [ - "model = create_model('efficientnet_b0', pretrained=False)\n", - "\n", - "with smart_open(MODEL_PATH, 'rb') as f:\n", - " buffer = io.BytesIO(f.read())\n", - " model = model.load_state_dict(torch.load(buffer))" + "server.run('route')" ] }, { "cell_type": "code", "execution_count": null, - "id": "5756afb5-963e-4f99-997b-263dd7795c8f", + "id": "f2a4956d-ce59-4f25-a5a4-929dc7da2d6c", "metadata": {}, "outputs": [], - "source": [ - "# server = ChaliceServer('image-classification',\n", - "# \"s3://gflow-models/efficientnet_b0.pth\",\n", - "# model_loader=load_model)" - ] + "source": [] }, { "cell_type": "code", diff --git a/examples/src/deployment/aws-lambda/app.py b/examples/src/deployment/aws-lambda/app.py new file mode 100644 index 00000000..9dfd9952 --- /dev/null +++ b/examples/src/deployment/aws-lambda/app.py @@ -0,0 +1,44 @@ +import io + +import numpy as np +import torch +from timm import create_model + +from chitra.core import load_imagenet_labels +from chitra.image import Chitra +from chitra.serve.cloud.aws_serverless import ChaliceServer + +MODEL_PATH = "s3://gflow-models/efficientnet_b0.pth" + +LABELS = load_imagenet_labels() + + +def preprocess(data) -> torch.Tensor: + image = Chitra(data) + image.resize((256, 256)) + x = image.numpy().astype(np.float32) + x = x / 255.0 + x = torch.from_numpy(x) + x = x.permute(2, 0, 1).unsqueeze(0) + return x + + +def postprocess(data: torch.Tensor) -> str: + return LABELS[data.argmax(1)] + + +def model_loader(buffer: io.BytesIO) -> torch.nn.Module: + model: torch.nn.Module = create_model("efficientnet_b0", pretrained=False) + model.load_state_dict(torch.load(buffer)) + return model + + +server = ChaliceServer( + "image-classification", + MODEL_PATH, + model_loader=model_loader, + preprocess_fn=preprocess, + postprocess_fn=postprocess, +) + +server.run("route") From e6aca293bc4ee0e915c44ccbef290dbbae286449 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 10:43:05 +0530 Subject: [PATCH 15/43] add example --- examples/nbs/09_chalice.ipynb | 217 ++++++++++++++++++ .../aws-lambda/.chalice/config.json | 9 + examples/src/deployment/aws-lambda/.gitignore | 2 + .../deployment/aws-lambda/requirements.txt | 1 + 4 files changed, 229 insertions(+) create mode 100644 examples/nbs/09_chalice.ipynb create mode 100644 examples/src/deployment/aws-lambda/.chalice/config.json create mode 100644 examples/src/deployment/aws-lambda/.gitignore create mode 100644 examples/src/deployment/aws-lambda/requirements.txt diff --git a/examples/nbs/09_chalice.ipynb b/examples/nbs/09_chalice.ipynb new file mode 100644 index 00000000..68bda52a --- /dev/null +++ b/examples/nbs/09_chalice.ipynb @@ -0,0 +1,217 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "30596049-1933-41e9-b189-9ab00f918116", + "metadata": {}, + "outputs": [], + "source": [ + "import io\n", + "\n", + "import numpy as np\n", + "import torch\n", + "from chitra.core import load_imagenet_labels\n", + "from chitra.serve.cloud.aws_serverless import ChaliceServer\n", + "from smart_open import open as smart_open\n", + "from timm import create_model, list_models" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25ce4031-9696-4f9e-bf81-7d1e8624e562", + "metadata": {}, + "outputs": [], + "source": [ + "import boto3\n", + "\n", + "s3 = boto3.client('s3')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67b705a4-6221-44b4-8638-c45a6c6281db", + "metadata": {}, + "outputs": [], + "source": [ + "def model_loader(buffer: io.BytesIO)-> torch.nn.Module:\n", + " model: torch.nn.Module = create_model('efficientnet_b0', pretrained=False)\n", + " model.load_state_dict(torch.load(buffer))\n", + " return model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e088dc0-49a2-4081-91a2-6d41b45ed7b2", + "metadata": {}, + "outputs": [], + "source": [ + "BUCKET = 'gflow-models'\n", + "FILE_OBJ = 'efficientnet_b0.pth'\n", + "MODEL_PATH = \"s3://gflow-models/efficientnet_b0.pth\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5756afb5-963e-4f99-997b-263dd7795c8f", + "metadata": {}, + "outputs": [], + "source": [ + "server = ChaliceServer('image-classification',\n", + " MODEL_PATH,\n", + " model_loader=model_loader)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b3a86e5-1d2f-46c7-8e68-d7da76652ccc", + "metadata": {}, + "outputs": [], + "source": [ + "server.run('route')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2a4956d-ce59-4f25-a5a4-929dc7da2d6c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c760e259-82e9-4bfc-a2d8-7e4b14684bc1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0cd48a18-b989-4699-a080-efd64f38b363", + "metadata": {}, + "outputs": [], + "source": [ + "from chitra.image import Chitra" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f5a448e6-5ce4-45ad-a0bc-a87878900cb5", + "metadata": {}, + "outputs": [], + "source": [ + "LABELS = load_imagenet_labels()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6feb93c-be27-403f-a1d4-4370ef0c7b58", + "metadata": {}, + "outputs": [], + "source": [ + "model = create_model(\n", + " 'efficientnet_b0',\n", + " pretrained=True,\n", + " checkpoint_path='./efficientnet_b0_ra-3dd342df.pth').eval()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ea121da-f2df-47c0-a09a-3d474752d321", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1910c175-8fe1-4b97-868b-38bb927b58bc", + "metadata": {}, + "outputs": [], + "source": [ + "image = Chitra(\n", + " \"https://ichef.bbci.co.uk/news/976/cpsprodpb/67CF/production/_108857562_mediaitem108857561.jpg\"\n", + ")\n", + "image.resize((256, 256))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77eba50d-bb2a-47a1-96a9-4cbefd998b45", + "metadata": {}, + "outputs": [], + "source": [ + "x = image.numpy().astype(np.float32)\n", + "x = x / 255.\n", + "x = torch.from_numpy(x)\n", + "\n", + "LABELS[model(x.permute(2, 0, 1).unsqueeze(0)).argmax(1)]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dcbb7d46-0c4f-4ada-9a80-65e3939950a8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8c3ada9-5e5a-4936-9407-5b7a88947ff8", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6eee0989-da5a-448f-a6bd-5a325adde7fc", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d044465c-f308-49b1-b827-33337b111182", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/src/deployment/aws-lambda/.chalice/config.json b/examples/src/deployment/aws-lambda/.chalice/config.json new file mode 100644 index 00000000..e8c9a94c --- /dev/null +++ b/examples/src/deployment/aws-lambda/.chalice/config.json @@ -0,0 +1,9 @@ +{ + "version": "2.0", + "app_name": "hello", + "stages": { + "dev": { + "api_gateway_stage": "api" + } + } +} diff --git a/examples/src/deployment/aws-lambda/.gitignore b/examples/src/deployment/aws-lambda/.gitignore new file mode 100644 index 00000000..db225aa0 --- /dev/null +++ b/examples/src/deployment/aws-lambda/.gitignore @@ -0,0 +1,2 @@ +../.chalice/deployments/ +../.chalice/venv/ diff --git a/examples/src/deployment/aws-lambda/requirements.txt b/examples/src/deployment/aws-lambda/requirements.txt new file mode 100644 index 00000000..11d3335b --- /dev/null +++ b/examples/src/deployment/aws-lambda/requirements.txt @@ -0,0 +1 @@ +chitra==0.2.0a0 From 98f23c06ef2057801cd457d2032a7138563eabb4 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 10:48:17 +0530 Subject: [PATCH 16/43] fix email --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0d496f1e..477a5954 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "flit_core.buildapi" [tool.flit.metadata] module = "chitra" author = "Aniket Maurya" -author-email = "hello@aniketmaury.com" +author-email = "hello@aniketmaurya.com" home-page = "https://github.com/aniketmaurya/chitra" classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", From 562441f3fdd38a3442f6c5f5111144fa98462855 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 14:20:36 +0530 Subject: [PATCH 17/43] fixes --- chitra/serve/base.py | 5 +++++ chitra/serve/cloud/aws_serverless.py | 19 ++++++++++++++----- examples/src/deployment/aws-lambda/app.py | 2 +- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/chitra/serve/base.py b/chitra/serve/base.py index e9de9587..8e269aad 100644 --- a/chitra/serve/base.py +++ b/chitra/serve/base.py @@ -25,6 +25,11 @@ def __init__( postprocess_conf: Optional[dict] = None, **kwargs, ): + if not preprocess_conf: + preprocess_conf = {} + if not postprocess_conf: + postprocess_conf = {} + self.api_type = api_type.upper() self.model = model self.preprocess_conf = preprocess_conf diff --git a/chitra/serve/cloud/aws_serverless.py b/chitra/serve/cloud/aws_serverless.py index af906042..844870dd 100644 --- a/chitra/serve/cloud/aws_serverless.py +++ b/chitra/serve/cloud/aws_serverless.py @@ -1,6 +1,7 @@ -from typing import Callable +from typing import Callable, List, Optional from chalice import Chalice, Rate +from loguru import logger from chitra.serve.cloud.base import CloudServer @@ -44,9 +45,12 @@ def __init__( def index(self): return {"hello": "world"} - def predict(self, x) -> dict: - data_processor = self.data_processor + def predict(self) -> dict: + data_processor = self.data_processor + x = self.app.current_request.raw_body + logger.debug(f"raw body type={type(x)}") + logger.debug(x) if data_processor.preprocess_fn: x = data_processor.preprocess(x, **self.preprocess_conf) x = self.model(x) @@ -54,8 +58,11 @@ def predict(self, x) -> dict: x = data_processor.postprocess(x, **self.postprocess_conf) return x - def run(self, invoke_method: str, **kwargs): + def run(self, invoke_method: str, content_types: Optional[List] = None, **kwargs): invoke_method = invoke_method.lower() + if not content_types: + content_types = [] + if invoke_method not in self.INVOKE_METHODS: raise NotImplementedError( f"invoke method={invoke_method} not implemented yet. Please select {self.INVOKE_METHODS}" @@ -64,4 +71,6 @@ def run(self, invoke_method: str, **kwargs): if invoke_method == "route": route_path = kwargs.get("path", "/predict") self.app.route("/", methods=["GET"])(self.index) - self.app.route(route_path, methods=["POST"])(self.predict) + self.app.route(route_path, methods=["POST"], content_types=content_types)( + self.predict + ) diff --git a/examples/src/deployment/aws-lambda/app.py b/examples/src/deployment/aws-lambda/app.py index 9dfd9952..8ebb667d 100644 --- a/examples/src/deployment/aws-lambda/app.py +++ b/examples/src/deployment/aws-lambda/app.py @@ -40,5 +40,5 @@ def model_loader(buffer: io.BytesIO) -> torch.nn.Module: preprocess_fn=preprocess, postprocess_fn=postprocess, ) - +app = server.app server.run("route") From 688fd5bac9b7eecbeb602c60b864ff9d74f81400 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 14:29:11 +0530 Subject: [PATCH 18/43] fixes --- chitra/__init__.py | 2 +- chitra/image.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/chitra/__init__.py b/chitra/__init__.py index 526de7df..8ad0c629 100644 --- a/chitra/__init__.py +++ b/chitra/__init__.py @@ -1,4 +1,4 @@ """Deep Learning library for Model Building, Interpretability, Visualization, API Building & Deployment.""" -__version__ = "0.2.0a0" +__version__ = "0.2.0a1" __license__ = "Apache License 2.0" diff --git a/chitra/image.py b/chitra/image.py index eb281ec3..6621d735 100644 --- a/chitra/image.py +++ b/chitra/image.py @@ -1,3 +1,4 @@ +import io import os from io import BytesIO from pathlib import Path @@ -89,6 +90,9 @@ def _load_image(data: DATA_FORMATS, cache: bool): if isinstance(data, Image.Image): return data + elif isinstance(data, bytes): + return Image.open(io.BytesIO(data)) + if isinstance(data, (tf.Tensor, torch.Tensor)): data = data.numpy().astype("uint8") From 8c7d92064ac51314a9aaeee4948a52e9dee4f7a0 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 16:04:50 +0530 Subject: [PATCH 19/43] fix example --- examples/src/deployment/aws-lambda/app.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/src/deployment/aws-lambda/app.py b/examples/src/deployment/aws-lambda/app.py index 8ebb667d..9b671eb6 100644 --- a/examples/src/deployment/aws-lambda/app.py +++ b/examples/src/deployment/aws-lambda/app.py @@ -8,14 +8,14 @@ from chitra.image import Chitra from chitra.serve.cloud.aws_serverless import ChaliceServer -MODEL_PATH = "s3://gflow-models/efficientnet_b0.pth" +MODEL_PATH = "../../../nbs/model.pth" LABELS = load_imagenet_labels() -def preprocess(data) -> torch.Tensor: - image = Chitra(data) - image.resize((256, 256)) +def preprocess(content_raw_body) -> torch.Tensor: + image = Chitra(content_raw_body) + image.resize((256, 256)).show() x = image.numpy().astype(np.float32) x = x / 255.0 x = torch.from_numpy(x) @@ -41,4 +41,4 @@ def model_loader(buffer: io.BytesIO) -> torch.nn.Module: postprocess_fn=postprocess, ) app = server.app -server.run("route") +server.run("route", content_types=["image/jpeg"]) From 8861a72f670cfccb9b65dd9ce469fb47f331b253 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 16:11:00 +0530 Subject: [PATCH 20/43] model.eval --- examples/src/deployment/aws-lambda/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/src/deployment/aws-lambda/app.py b/examples/src/deployment/aws-lambda/app.py index 9b671eb6..a52c1b3d 100644 --- a/examples/src/deployment/aws-lambda/app.py +++ b/examples/src/deployment/aws-lambda/app.py @@ -2,6 +2,7 @@ import numpy as np import torch +from loguru import logger from timm import create_model from chitra.core import load_imagenet_labels @@ -11,6 +12,7 @@ MODEL_PATH = "../../../nbs/model.pth" LABELS = load_imagenet_labels() +logger.debug(f"labels={LABELS[:5]}...") def preprocess(content_raw_body) -> torch.Tensor: @@ -24,11 +26,13 @@ def preprocess(content_raw_body) -> torch.Tensor: def postprocess(data: torch.Tensor) -> str: - return LABELS[data.argmax(1)] + logger.debug(f"predictions = {data}") + result = LABELS[data.argmax(1)] + return result def model_loader(buffer: io.BytesIO) -> torch.nn.Module: - model: torch.nn.Module = create_model("efficientnet_b0", pretrained=False) + model = create_model("efficientnet_b0", pretrained=False).eval() model.load_state_dict(torch.load(buffer)) return model From f0a08820db3ab5218df8302fa90063722315364f Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 16:12:31 +0530 Subject: [PATCH 21/43] remove .show --- examples/src/deployment/aws-lambda/app.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/src/deployment/aws-lambda/app.py b/examples/src/deployment/aws-lambda/app.py index a52c1b3d..82c704ba 100644 --- a/examples/src/deployment/aws-lambda/app.py +++ b/examples/src/deployment/aws-lambda/app.py @@ -17,7 +17,7 @@ def preprocess(content_raw_body) -> torch.Tensor: image = Chitra(content_raw_body) - image.resize((256, 256)).show() + image.resize((256, 256)) x = image.numpy().astype(np.float32) x = x / 255.0 x = torch.from_numpy(x) @@ -36,10 +36,9 @@ def model_loader(buffer: io.BytesIO) -> torch.nn.Module: model.load_state_dict(torch.load(buffer)) return model - server = ChaliceServer( - "image-classification", - MODEL_PATH, + api_type="image-classification", + model_path=MODEL_PATH, model_loader=model_loader, preprocess_fn=preprocess, postprocess_fn=postprocess, From dd452b451b499c244c4110f20d512a932be3522e Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 16:27:40 +0530 Subject: [PATCH 22/43] add test --- examples/nbs/09_chalice.ipynb | 24 ++++++++++++++++- examples/src/deployment/aws-lambda/app.py | 1 + tests/serve/test_cloud/__init__.py | 0 tests/serve/test_cloud/test_aws_serverless.py | 0 tests/serve/test_cloud/tets_base.py | 26 +++++++++++++++++++ 5 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 tests/serve/test_cloud/__init__.py create mode 100644 tests/serve/test_cloud/test_aws_serverless.py create mode 100644 tests/serve/test_cloud/tets_base.py diff --git a/examples/nbs/09_chalice.ipynb b/examples/nbs/09_chalice.ipynb index 68bda52a..9117571f 100644 --- a/examples/nbs/09_chalice.ipynb +++ b/examples/nbs/09_chalice.ipynb @@ -168,13 +168,35 @@ "outputs": [], "source": [] }, + { + "cell_type": "code", + "execution_count": null, + "id": "e792f044-66d1-47c4-b28b-552800fe45fe", + "metadata": {}, + "outputs": [], + "source": [ + "# https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e939c1e-53b5-416f-910f-b00e3726244b", + "metadata": {}, + "outputs": [], + "source": [ + "ls /Users/aniket/.cache/torch/hub/checkpoints/efficient*" + ] + }, { "cell_type": "code", "execution_count": null, "id": "a8c3ada9-5e5a-4936-9407-5b7a88947ff8", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "create_model('efficientnet_b1', pretrained=True)" + ] }, { "cell_type": "code", diff --git a/examples/src/deployment/aws-lambda/app.py b/examples/src/deployment/aws-lambda/app.py index 82c704ba..537841d4 100644 --- a/examples/src/deployment/aws-lambda/app.py +++ b/examples/src/deployment/aws-lambda/app.py @@ -36,6 +36,7 @@ def model_loader(buffer: io.BytesIO) -> torch.nn.Module: model.load_state_dict(torch.load(buffer)) return model + server = ChaliceServer( api_type="image-classification", model_path=MODEL_PATH, diff --git a/tests/serve/test_cloud/__init__.py b/tests/serve/test_cloud/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/serve/test_cloud/test_aws_serverless.py b/tests/serve/test_cloud/test_aws_serverless.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/serve/test_cloud/tets_base.py b/tests/serve/test_cloud/tets_base.py new file mode 100644 index 00000000..ae7a78c1 --- /dev/null +++ b/tests/serve/test_cloud/tets_base.py @@ -0,0 +1,26 @@ +import io + +import torch +from chalice import Chalice +from timm import create_model + +from chitra.serve.cloud.base import CloudServer + +MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" + + +def model_loader(buffer: io.BytesIO) -> torch.nn.Module: + model: torch.nn.Module = create_model("efficientnet_b0", pretrained=False).eval() + model.load_state_dict(torch.load(buffer)) + return model + + +def test_cloudserver(): + server = CloudServer( + "image-classification", + model_path=MODEL_PATH, + model_loader=model_loader + ) + + assert isinstance(server, Chalice) + assert isinstance(server.model, torch.nn.Module) From 375b577d1963445f6b8d8b6e95438f6998d30b81 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Sat, 14 Aug 2021 10:57:58 +0000 Subject: [PATCH 23/43] Format code with black --- tests/serve/test_cloud/tets_base.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/serve/test_cloud/tets_base.py b/tests/serve/test_cloud/tets_base.py index ae7a78c1..7f4d6774 100644 --- a/tests/serve/test_cloud/tets_base.py +++ b/tests/serve/test_cloud/tets_base.py @@ -17,9 +17,7 @@ def model_loader(buffer: io.BytesIO) -> torch.nn.Module: def test_cloudserver(): server = CloudServer( - "image-classification", - model_path=MODEL_PATH, - model_loader=model_loader + "image-classification", model_path=MODEL_PATH, model_loader=model_loader ) assert isinstance(server, Chalice) From c3667a6a18590add124eea9aab448022ad2aae0b Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 16:32:27 +0530 Subject: [PATCH 24/43] fixes --- chitra/serve/cloud/base.py | 6 +++--- tests/serve/test_cloud/tets_base.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chitra/serve/cloud/base.py b/chitra/serve/cloud/base.py index 9c3a4621..f2362535 100644 --- a/chitra/serve/cloud/base.py +++ b/chitra/serve/cloud/base.py @@ -1,6 +1,6 @@ import io from abc import ABC -from typing import Callable +from typing import Callable, Optional import smart_open @@ -13,8 +13,8 @@ def __init__( api_type: str, model_path: str, model_loader: Callable, - preprocess_fn: Callable, - postprocess_fn: Callable, + preprocess_fn: Optional[Callable] = None, + postprocess_fn: Optional[Callable] = None, **kwargs ): raw_model = self.download_model(model_path, **kwargs) diff --git a/tests/serve/test_cloud/tets_base.py b/tests/serve/test_cloud/tets_base.py index ae7a78c1..69b5a05f 100644 --- a/tests/serve/test_cloud/tets_base.py +++ b/tests/serve/test_cloud/tets_base.py @@ -19,7 +19,7 @@ def test_cloudserver(): server = CloudServer( "image-classification", model_path=MODEL_PATH, - model_loader=model_loader + model_loader=model_loader, ) assert isinstance(server, Chalice) From 5c2c36065136227239dcdbce55dce5f86203107f Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 16:35:16 +0530 Subject: [PATCH 25/43] rename job --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b3596274..6fd7b827 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Python Main CI +name: pytest on: push: branches: [ master ] From 05d061d93f0e1e5d73759e2a506f6d3363791a7d Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Sat, 14 Aug 2021 11:10:06 +0000 Subject: [PATCH 26/43] Autofix issues in 4 files Resolved issues in the following files via DeepSource Autofix: 1. chitra/image.py 2. chitra/serve/cloud/aws_serverless.py 3. chitra/serve/cloud/base.py 4. tests/serve/test_cloud/test_aws_serverless.py --- chitra/image.py | 2 +- chitra/serve/cloud/aws_serverless.py | 3 ++- chitra/serve/cloud/base.py | 3 ++- tests/serve/test_cloud/test_aws_serverless.py | 0 4 files changed, 5 insertions(+), 3 deletions(-) delete mode 100644 tests/serve/test_cloud/test_aws_serverless.py diff --git a/chitra/image.py b/chitra/image.py index 6621d735..e9d2db37 100644 --- a/chitra/image.py +++ b/chitra/image.py @@ -90,7 +90,7 @@ def _load_image(data: DATA_FORMATS, cache: bool): if isinstance(data, Image.Image): return data - elif isinstance(data, bytes): + if isinstance(data, bytes): return Image.open(io.BytesIO(data)) if isinstance(data, (tf.Tensor, torch.Tensor)): diff --git a/chitra/serve/cloud/aws_serverless.py b/chitra/serve/cloud/aws_serverless.py index 844870dd..531cf9ac 100644 --- a/chitra/serve/cloud/aws_serverless.py +++ b/chitra/serve/cloud/aws_serverless.py @@ -42,7 +42,8 @@ def __init__( self.app = Chalice(app_name=kwargs.get("name", "chitra-server")) - def index(self): + @staticmethod + def index(): return {"hello": "world"} def predict(self) -> dict: diff --git a/chitra/serve/cloud/base.py b/chitra/serve/cloud/base.py index f2362535..b6844a09 100644 --- a/chitra/serve/cloud/base.py +++ b/chitra/serve/cloud/base.py @@ -28,7 +28,8 @@ def __init__( **kwargs ) - def download_model(self, path: str, **kwargs) -> io.BytesIO: + @staticmethod + def download_model(path: str, **kwargs) -> io.BytesIO: """ Download model from cloud ref: http://5.9.10.113/67706477/load-pytorch-model-from-s3-bucket diff --git a/tests/serve/test_cloud/test_aws_serverless.py b/tests/serve/test_cloud/test_aws_serverless.py deleted file mode 100644 index e69de29b..00000000 From 648659c657f0fd038023252e1ea213494cb07d6f Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 16:44:32 +0530 Subject: [PATCH 27/43] fix missing test --- tests/serve/{test_cloud => cloud}/__init__.py | 0 tests/serve/{test_cloud/tets_base.py => cloud/test_base.py} | 1 - 2 files changed, 1 deletion(-) rename tests/serve/{test_cloud => cloud}/__init__.py (100%) rename tests/serve/{test_cloud/tets_base.py => cloud/test_base.py} (99%) diff --git a/tests/serve/test_cloud/__init__.py b/tests/serve/cloud/__init__.py similarity index 100% rename from tests/serve/test_cloud/__init__.py rename to tests/serve/cloud/__init__.py diff --git a/tests/serve/test_cloud/tets_base.py b/tests/serve/cloud/test_base.py similarity index 99% rename from tests/serve/test_cloud/tets_base.py rename to tests/serve/cloud/test_base.py index 69b5a05f..f139c2df 100644 --- a/tests/serve/test_cloud/tets_base.py +++ b/tests/serve/cloud/test_base.py @@ -21,6 +21,5 @@ def test_cloudserver(): model_path=MODEL_PATH, model_loader=model_loader, ) - assert isinstance(server, Chalice) assert isinstance(server.model, torch.nn.Module) From 384950e5a2d6a0342c387e8c9745242ec460f4fd Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 16:51:34 +0530 Subject: [PATCH 28/43] add timm dep --- .github/workflows/main.yml | 1 + chitra/serve/cloud/aws_serverless.py | 8 -------- chitra/serve/cloud/base.py | 4 +++- pyproject.toml | 3 +++ 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6fd7b827..14914fcf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,6 +35,7 @@ jobs: pip install ".[serve]" pip install ".[converter]" pip install ".[test]" + pip install ".[example]" pip list shell: bash diff --git a/chitra/serve/cloud/aws_serverless.py b/chitra/serve/cloud/aws_serverless.py index 531cf9ac..de6b99d7 100644 --- a/chitra/serve/cloud/aws_serverless.py +++ b/chitra/serve/cloud/aws_serverless.py @@ -11,14 +11,6 @@ RATE_UNIT = {"m": Rate.MINUTES, "h": Rate.HOURS, "d": Rate.DAYS} -def infer_location_type(path: str): - if path.startswith("s3"): - return S3 - if path.startswith("gcs"): - return GCS - raise ValueError(f"Location type is not supported yet for path={path}") - - class ChaliceServer(CloudServer): INVOKE_METHODS = ("route",) diff --git a/chitra/serve/cloud/base.py b/chitra/serve/cloud/base.py index b6844a09..c20093aa 100644 --- a/chitra/serve/cloud/base.py +++ b/chitra/serve/cloud/base.py @@ -1,3 +1,4 @@ +import abc import io from abc import ABC from typing import Callable, Optional @@ -45,5 +46,6 @@ def download_model(path: str, **kwargs) -> io.BytesIO: data = io.BytesIO(fr.read()) return data - def run(self, *_, **__): + @abc.abstractmethod + def run(self): raise NotImplementedError diff --git a/pyproject.toml b/pyproject.toml index 477a5954..9e1496d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,9 @@ test = [ "pytest-asyncio", "coverage", ] +example = [ + "timm", +] [tool.isort] profile = "black" From 49da652c5724dbf6de417feb5060463eb305e263 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 16:58:39 +0530 Subject: [PATCH 29/43] remove loguru direct import --- chitra/serve/cloud/aws_serverless.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chitra/serve/cloud/aws_serverless.py b/chitra/serve/cloud/aws_serverless.py index de6b99d7..7c1ae4bc 100644 --- a/chitra/serve/cloud/aws_serverless.py +++ b/chitra/serve/cloud/aws_serverless.py @@ -1,8 +1,8 @@ from typing import Callable, List, Optional from chalice import Chalice, Rate -from loguru import logger +from chitra.logging import logger from chitra.serve.cloud.base import CloudServer S3 = "s3" From 0b0b72b58cb1fc5ac01c926f8085a8886a72557e Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 17:01:38 +0530 Subject: [PATCH 30/43] cache pip :package: --- .github/workflows/main.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 14914fcf..78f771a5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,6 +27,17 @@ jobs: with: python-version: 3.7 + - name: Cache pip + uses: actions/cache@v2 + with: + # This path is specific to Ubuntu + path: ~/.cache/pip + # Look to see if there is a cache hit for the corresponding requirements file + key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Installation run: | python --version From 5379987bb4a3673041a8c08ef9bcc172b57fe1cc Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 17:10:47 +0530 Subject: [PATCH 31/43] fix tests --- .github/workflows/main.yml | 14 +++++++++----- examples/src/deployment/aws-lambda/app.py | 2 +- .../cloud/{test_base.py => test_chalice_server.py} | 9 +++++---- 3 files changed, 15 insertions(+), 10 deletions(-) rename tests/serve/cloud/{test_base.py => test_chalice_server.py} (62%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 78f771a5..9fe28399 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,6 +12,13 @@ jobs: strategy: matrix: os: [ ubuntu-latest, macos-latest ] + include: + - os: ubuntu-latest + path: ~/.cache/pip + - os: macos-latest + path: ~/Library/Caches/pip + - os: windows-latest + path: ~\AppData\Local\pip\Cache env: OS: ${{ matrix.os }} PYTHON: '3.7' @@ -30,13 +37,10 @@ jobs: - name: Cache pip uses: actions/cache@v2 with: - # This path is specific to Ubuntu - path: ~/.cache/pip - # Look to see if there is a cache hit for the corresponding requirements file - key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} + path: ${{ matrix.path }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - ${{ runner.os }}- - name: Installation run: | diff --git a/examples/src/deployment/aws-lambda/app.py b/examples/src/deployment/aws-lambda/app.py index 537841d4..0898f8ca 100644 --- a/examples/src/deployment/aws-lambda/app.py +++ b/examples/src/deployment/aws-lambda/app.py @@ -9,7 +9,7 @@ from chitra.image import Chitra from chitra.serve.cloud.aws_serverless import ChaliceServer -MODEL_PATH = "../../../nbs/model.pth" +MODEL_PATH = "../../../assets/model.pth" LABELS = load_imagenet_labels() logger.debug(f"labels={LABELS[:5]}...") diff --git a/tests/serve/cloud/test_base.py b/tests/serve/cloud/test_chalice_server.py similarity index 62% rename from tests/serve/cloud/test_base.py rename to tests/serve/cloud/test_chalice_server.py index f139c2df..e6b08dac 100644 --- a/tests/serve/cloud/test_base.py +++ b/tests/serve/cloud/test_chalice_server.py @@ -4,9 +4,10 @@ from chalice import Chalice from timm import create_model -from chitra.serve.cloud.base import CloudServer +from chitra.serve.cloud import ChaliceServer -MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" +# MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" +MODEL_PATH = "examples/assets/model.pth" def model_loader(buffer: io.BytesIO) -> torch.nn.Module: @@ -16,10 +17,10 @@ def model_loader(buffer: io.BytesIO) -> torch.nn.Module: def test_cloudserver(): - server = CloudServer( + server = ChaliceServer( "image-classification", model_path=MODEL_PATH, model_loader=model_loader, ) - assert isinstance(server, Chalice) + assert isinstance(server.app, Chalice) assert isinstance(server.model, torch.nn.Module) From 7115f5e789058f8e89b71f33c6f2cb1a3783c0df Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 17:14:28 +0530 Subject: [PATCH 32/43] fix tests --- .github/workflows/main.yml | 2 -- tests/serve/cloud/test_chalice_server.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9fe28399..6ecf7574 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,8 +17,6 @@ jobs: path: ~/.cache/pip - os: macos-latest path: ~/Library/Caches/pip - - os: windows-latest - path: ~\AppData\Local\pip\Cache env: OS: ${{ matrix.os }} PYTHON: '3.7' diff --git a/tests/serve/cloud/test_chalice_server.py b/tests/serve/cloud/test_chalice_server.py index e6b08dac..4b0f37d5 100644 --- a/tests/serve/cloud/test_chalice_server.py +++ b/tests/serve/cloud/test_chalice_server.py @@ -6,8 +6,8 @@ from chitra.serve.cloud import ChaliceServer -# MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" -MODEL_PATH = "examples/assets/model.pth" +MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" +# MODEL_PATH = "examples/assets/model.pth" def model_loader(buffer: io.BytesIO) -> torch.nn.Module: From 4dc2705451c68d24ed3d7d25ea99b474e7091c85 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 17:29:41 +0530 Subject: [PATCH 33/43] fix tests --- .github/workflows/main.yml | 1 + chitra/serve/app.py | 5 ++++- chitra/serve/base.py | 2 +- tests/serve/test_app.py | 5 +++++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6ecf7574..00b87cf8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,6 +39,7 @@ jobs: key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- + ${{ runner.os }}- - name: Installation run: | diff --git a/chitra/serve/app.py b/chitra/serve/app.py index 07953189..bff9900f 100644 --- a/chitra/serve/app.py +++ b/chitra/serve/app.py @@ -9,7 +9,10 @@ class GradioApp(ModelServer): - API_TYPES = {"VISION": (const.IMAGE_CLF, const.OBJECT_DETECTION)} + API_TYPES = { + "VISION": (const.IMAGE_CLF, const.OBJECT_DETECTION), + "NLP": (const.TXT_CLF,), + } def __init__( self, diff --git a/chitra/serve/base.py b/chitra/serve/base.py index 8e269aad..d74233ed 100644 --- a/chitra/serve/base.py +++ b/chitra/serve/base.py @@ -59,7 +59,7 @@ def set_default_processor(self) -> DataProcessor: elif api_type in ModelServer.API_TYPES.get("NLP"): self.data_processor = DefaultTextProcessor.nlp else: - raise UserWarning( + raise NotImplementedError( f"{api_type} is not implemented! Available types are -\ {ModelServer.get_available_api_types()}" ) diff --git a/tests/serve/test_app.py b/tests/serve/test_app.py index dfaa8f7f..e77b26ef 100644 --- a/tests/serve/test_app.py +++ b/tests/serve/test_app.py @@ -75,3 +75,8 @@ def test_run(mock_gr, test_input, expected): description=app.desc, **expected, ) + + +def test_setup(): + with pytest.raises(NotImplementedError): + GradioApp("RANDOM", model=dummy_model) From 80c4417dac7e5f03420de7ef70540ceb124376a1 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 17:31:00 +0530 Subject: [PATCH 34/43] add test --- tests/serve/test_app.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/serve/test_app.py b/tests/serve/test_app.py index e77b26ef..75d6f5aa 100644 --- a/tests/serve/test_app.py +++ b/tests/serve/test_app.py @@ -80,3 +80,8 @@ def test_run(mock_gr, test_input, expected): def test_setup(): with pytest.raises(NotImplementedError): GradioApp("RANDOM", model=dummy_model) + + with pytest.raises(NotImplementedError): + app = GradioApp(const.IMAGE_CLF, model=dummy_model) + app.api_type = "RANDOM" + app.setup() From d11efe455af6569993e8a549461eef04842a5111 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 17:33:12 +0530 Subject: [PATCH 35/43] add test --- tests/test_image.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_image.py b/tests/test_image.py index ab7762d0..097d589a 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -60,3 +60,9 @@ def test__cache_image(): image.save = MagicMock() _cache_image(image, "test_image.jpg") image.save.assert_called_once() + + +def test_image_resize(): + image = Chitra(url, cache=True) + image.resize((224, 224)) + assert image.shape[:2] == (224, 224) From 0600714f351bba83110900caaa70e3f11e0b497e Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 17:40:49 +0530 Subject: [PATCH 36/43] add test --- chitra/serve/cloud/aws_serverless.py | 1 - tests/serve/cloud/test_chalice_server.py | 24 ++++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/chitra/serve/cloud/aws_serverless.py b/chitra/serve/cloud/aws_serverless.py index 7c1ae4bc..0aac33eb 100644 --- a/chitra/serve/cloud/aws_serverless.py +++ b/chitra/serve/cloud/aws_serverless.py @@ -43,7 +43,6 @@ def predict(self) -> dict: data_processor = self.data_processor x = self.app.current_request.raw_body logger.debug(f"raw body type={type(x)}") - logger.debug(x) if data_processor.preprocess_fn: x = data_processor.preprocess(x, **self.preprocess_conf) x = self.model(x) diff --git a/tests/serve/cloud/test_chalice_server.py b/tests/serve/cloud/test_chalice_server.py index 4b0f37d5..8a704007 100644 --- a/tests/serve/cloud/test_chalice_server.py +++ b/tests/serve/cloud/test_chalice_server.py @@ -6,8 +6,11 @@ from chitra.serve.cloud import ChaliceServer -MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" -# MODEL_PATH = "examples/assets/model.pth" +# MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" +MODEL_PATH = "examples/assets/model.pth" +url = ( + "https://raw.githubusercontent.com/aniketmaurya/chitra/master/docs/assets/logo.png" +) def model_loader(buffer: io.BytesIO) -> torch.nn.Module: @@ -24,3 +27,20 @@ def test_cloudserver(): ) assert isinstance(server.app, Chalice) assert isinstance(server.model, torch.nn.Module) + + +def test_index(): + assert ChaliceServer.index() == {"hello": "world"} + + +def test_predict(): + class Dummy: + raw = url + + server = ChaliceServer( + "image-classification", + model_path=MODEL_PATH, + model_loader=model_loader, + ) + server.app.current_request = Dummy + assert isinstance(server.predict(), str) From 9465b6fe355e7756724ec1ff41873a0b861d1104 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 17:41:23 +0530 Subject: [PATCH 37/43] fix --- tests/serve/cloud/test_chalice_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/serve/cloud/test_chalice_server.py b/tests/serve/cloud/test_chalice_server.py index 8a704007..071bfc18 100644 --- a/tests/serve/cloud/test_chalice_server.py +++ b/tests/serve/cloud/test_chalice_server.py @@ -35,7 +35,7 @@ def test_index(): def test_predict(): class Dummy: - raw = url + raw_body = url server = ChaliceServer( "image-classification", From 6e8ec8a2996c7a5abfe68aae022e3a61d3b7da34 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 17:49:58 +0530 Subject: [PATCH 38/43] fixes --- tests/serve/cloud/test_chalice_server.py | 25 ++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tests/serve/cloud/test_chalice_server.py b/tests/serve/cloud/test_chalice_server.py index 071bfc18..3f5e91ec 100644 --- a/tests/serve/cloud/test_chalice_server.py +++ b/tests/serve/cloud/test_chalice_server.py @@ -1,18 +1,37 @@ import io +import numpy as np import torch from chalice import Chalice from timm import create_model +from chitra.core import load_imagenet_labels +from chitra.image import Chitra from chitra.serve.cloud import ChaliceServer -# MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" -MODEL_PATH = "examples/assets/model.pth" +LABELS = load_imagenet_labels() + +MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" + url = ( "https://raw.githubusercontent.com/aniketmaurya/chitra/master/docs/assets/logo.png" ) +def preprocess(content_raw_body) -> torch.Tensor: + image = Chitra(content_raw_body) + image.resize((256, 256)) + x = image.numpy().astype(np.float32) + x = x / 255.0 + x = torch.from_numpy(x) + x = x.permute(2, 0, 1).unsqueeze(0) + return x + + +def postprocess(data: torch.Tensor) -> str: + return LABELS[data.argmax(1)] + + def model_loader(buffer: io.BytesIO) -> torch.nn.Module: model: torch.nn.Module = create_model("efficientnet_b0", pretrained=False).eval() model.load_state_dict(torch.load(buffer)) @@ -41,6 +60,8 @@ class Dummy: "image-classification", model_path=MODEL_PATH, model_loader=model_loader, + preprocess_fn=preprocess, + postprocess_fn=postprocess ) server.app.current_request = Dummy assert isinstance(server.predict(), str) From b8005cf53ebb014486deaafedf2d0ce1b10e7c51 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Sat, 14 Aug 2021 12:20:12 +0000 Subject: [PATCH 39/43] Format code with black --- tests/serve/cloud/test_chalice_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/serve/cloud/test_chalice_server.py b/tests/serve/cloud/test_chalice_server.py index 3f5e91ec..b8f8418b 100644 --- a/tests/serve/cloud/test_chalice_server.py +++ b/tests/serve/cloud/test_chalice_server.py @@ -61,7 +61,7 @@ class Dummy: model_path=MODEL_PATH, model_loader=model_loader, preprocess_fn=preprocess, - postprocess_fn=postprocess + postprocess_fn=postprocess, ) server.app.current_request = Dummy assert isinstance(server.predict(), str) From 11dcaf1105510dc018226d99dc8439bb223c3729 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 17:55:27 +0530 Subject: [PATCH 40/43] remove abc method run --- chitra/serve/cloud/base.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/chitra/serve/cloud/base.py b/chitra/serve/cloud/base.py index c20093aa..bc764be3 100644 --- a/chitra/serve/cloud/base.py +++ b/chitra/serve/cloud/base.py @@ -45,7 +45,3 @@ def download_model(path: str, **kwargs) -> io.BytesIO: with smart_open.open(path, mode="rb", **kwargs) as fr: data = io.BytesIO(fr.read()) return data - - @abc.abstractmethod - def run(self): - raise NotImplementedError From 46f923d375a512ab4f625a23441ac10f3965720e Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 18:00:00 +0530 Subject: [PATCH 41/43] update --- chitra/serve/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/chitra/serve/base.py b/chitra/serve/base.py index d74233ed..96e1a6ad 100644 --- a/chitra/serve/base.py +++ b/chitra/serve/base.py @@ -1,3 +1,4 @@ +import abc import itertools from typing import Callable, List, Optional @@ -65,5 +66,6 @@ def set_default_processor(self) -> DataProcessor: ) return self.data_processor - def run(self, *_, **__): - raise NotImplementedError + @abc.abstractmethod + def run(self): + pass From fedd53913a534c13d5d43ae66c6ab90b98e9e004 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 18:06:27 +0530 Subject: [PATCH 42/43] fix tests --- Makefile | 2 +- chitra/serve/base.py | 2 +- tests/serve/cloud/test_chalice_server.py | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 68d14993..507573f1 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build_docs build +.PHONY: build_docs clean style build pypi build_docs: cp README.md docs/index.md diff --git a/chitra/serve/base.py b/chitra/serve/base.py index 96e1a6ad..514061de 100644 --- a/chitra/serve/base.py +++ b/chitra/serve/base.py @@ -68,4 +68,4 @@ def set_default_processor(self) -> DataProcessor: @abc.abstractmethod def run(self): - pass + raise NotImplementedError diff --git a/tests/serve/cloud/test_chalice_server.py b/tests/serve/cloud/test_chalice_server.py index b8f8418b..ff1267d2 100644 --- a/tests/serve/cloud/test_chalice_server.py +++ b/tests/serve/cloud/test_chalice_server.py @@ -1,4 +1,5 @@ import io +import os import numpy as np import torch @@ -11,7 +12,9 @@ LABELS = load_imagenet_labels() -MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" +MODEL_PATH = "examples/assets/model.pth" +if not os.path.exists(MODEL_PATH): + MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" url = ( "https://raw.githubusercontent.com/aniketmaurya/chitra/master/docs/assets/logo.png" From 16322b6e8be7e0d643b25cd69a1379483cfd47e2 Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Sat, 14 Aug 2021 18:09:38 +0530 Subject: [PATCH 43/43] refactor --- chitra/serve/base.py | 4 ---- tests/serve/cloud/test_chalice_server.py | 3 ++- tests/serve/test_model_server.py | 9 --------- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/chitra/serve/base.py b/chitra/serve/base.py index 514061de..350a3383 100644 --- a/chitra/serve/base.py +++ b/chitra/serve/base.py @@ -65,7 +65,3 @@ def set_default_processor(self) -> DataProcessor: {ModelServer.get_available_api_types()}" ) return self.data_processor - - @abc.abstractmethod - def run(self): - raise NotImplementedError diff --git a/tests/serve/cloud/test_chalice_server.py b/tests/serve/cloud/test_chalice_server.py index ff1267d2..ace47a2f 100644 --- a/tests/serve/cloud/test_chalice_server.py +++ b/tests/serve/cloud/test_chalice_server.py @@ -14,7 +14,8 @@ MODEL_PATH = "examples/assets/model.pth" if not os.path.exists(MODEL_PATH): - MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/efficientnet_b0_ra-3dd342df.pth" + MODEL_PATH = "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/\ + efficientnet_b0_ra-3dd342df.pth" url = ( "https://raw.githubusercontent.com/aniketmaurya/chitra/master/docs/assets/logo.png" diff --git a/tests/serve/test_model_server.py b/tests/serve/test_model_server.py index c5c21633..c3a23d1d 100644 --- a/tests/serve/test_model_server.py +++ b/tests/serve/test_model_server.py @@ -1,7 +1,4 @@ -import pytest - from chitra.serve import ModelServer -from chitra.serve import constants as const def dummy_model(x: str): @@ -11,9 +8,3 @@ def dummy_model(x: str): def test_get_available_api_types(): model_server = ModelServer.get_available_api_types() assert isinstance(model_server, list) - - -def test_model_server_run(): - model_server = ModelServer(api_type=const.TXT_CLF, model=dummy_model) - with pytest.raises(NotImplementedError): - model_server.run()