From 5c03db8ff4d3b6c84d1c947ff8899d791b0e782c Mon Sep 17 00:00:00 2001 From: greg pereira Date: Tue, 16 Apr 2024 10:01:17 -0700 Subject: [PATCH] object detection recipe and MS tests Signed-off-by: greg pereira Signed-off-by: Gregory-Pereira Signed-off-by: greg pereira --- .github/workflows/model_servers.yaml | 36 +++++++------ model_servers/common/Makefile.common | 2 +- .../object_detection_python/Makefile | 40 +++++++++----- .../base/Containerfile | 5 +- .../src/object_detection_server.py | 6 ++- .../object_detection_python/src/run.sh | 10 ++++ .../object_detection_python/tests/__init__.py | 0 .../object_detection_python/tests/conftest.py | 45 ++++++++++++++++ .../tests/requirements.txt | 8 +++ .../tests/test_alive.py | 17 ++++++ models/Makefile | 7 +++ .../{client => app}/Containerfile | 0 .../object_detection_client.py | 4 +- .../object_detection/app/requirements.txt | 40 ++++++++++++++ .../object_detection/client/requirements.txt | 3 -- .../object_detection/tests/__init__.py | 0 .../object_detection/tests/conftest.py | 54 +++++++++++++++++++ .../object_detection/tests/requirements.txt | 8 +++ .../object_detection/tests/test_alive.py | 12 +++++ 19 files changed, 258 insertions(+), 39 deletions(-) create mode 100644 model_servers/object_detection_python/src/run.sh create mode 100644 model_servers/object_detection_python/tests/__init__.py create mode 100644 model_servers/object_detection_python/tests/conftest.py create mode 100644 model_servers/object_detection_python/tests/requirements.txt create mode 100644 model_servers/object_detection_python/tests/test_alive.py rename recipes/computer_vision/object_detection/{client => app}/Containerfile (100%) rename recipes/computer_vision/object_detection/{client => app}/object_detection_client.py (92%) create mode 100644 recipes/computer_vision/object_detection/app/requirements.txt delete mode 100644 recipes/computer_vision/object_detection/client/requirements.txt create mode 100644 recipes/computer_vision/object_detection/tests/__init__.py create mode 100644 recipes/computer_vision/object_detection/tests/conftest.py create mode 100644 recipes/computer_vision/object_detection/tests/requirements.txt create mode 100644 recipes/computer_vision/object_detection/tests/test_alive.py diff --git a/.github/workflows/model_servers.yaml b/.github/workflows/model_servers.yaml index dd4324daf..7655aad9a 100644 --- a/.github/workflows/model_servers.yaml +++ b/.github/workflows/model_servers.yaml @@ -26,23 +26,29 @@ jobs: strategy: matrix: include: - - image_name: llamacpp_python - model: granite + # - image_name: llamacpp_python + # model: granite + # flavor: base + # directory: llamacpp_python + # platforms: linux/amd64,linux/arm64 + # no_gpu: 1 + # - image_name: llamacpp_python_cuda + # model: granite + # flavor: cuda + # directory: llamacpp_python + # platforms: linux/amd64 + # cuda: 1 + # - image_name: whispercpp + # model: whisper-small + # flavor: base + # directory: whispercpp + # platforms: linux/amd64,linux/arm64 + # no_gpu: 1 + - image_name: object_detection_python + model: facebook-detr-resnet-101 flavor: base - directory: llamacpp_python - platforms: linux/amd64,linux/arm64 - no_gpu: 1 - - image_name: llamacpp_python_cuda - model: granite - flavor: cuda - directory: llamacpp_python + directory: object_detection_python platforms: linux/amd64 - cuda: 1 - - image_name: whispercpp - model: whisper-small - flavor: base - directory: whispercpp - platforms: linux/amd64,linux/arm64 no_gpu: 1 runs-on: ubuntu-22.04 permissions: diff --git a/model_servers/common/Makefile.common b/model_servers/common/Makefile.common index ee249451c..b0fff2634 100644 --- a/model_servers/common/Makefile.common +++ b/model_servers/common/Makefile.common @@ -10,7 +10,7 @@ endif .PHONY: build build: - podman build --squash-all --build-arg $(PORT) -t $(IMAGE) . -f base/Containerfile + podman build --squash-all --build-arg PORT=$(PORT) -t $(IMAGE) . -f base/Containerfile .PHONY: install install: diff --git a/model_servers/object_detection_python/Makefile b/model_servers/object_detection_python/Makefile index 22bc7c04a..589fdc3c2 100644 --- a/model_servers/object_detection_python/Makefile +++ b/model_servers/object_detection_python/Makefile @@ -1,35 +1,47 @@ APP := object_detection_python PORT ?= 8000 -include ../common/Makefile.common +REGISTRY ?= ghcr.io +REGISTRY_ORG ?= containers -IMAGE_NAME ?= $(REGISTRY_ORG)/$(COMPONENT)/$(APP):latest +MODEL_NAME ?= facebook/detr-resnet-101 +MODELS_DIR := /app/models + +# include ../common/Makefile.common + +IMAGE_NAME ?= $(REGISTRY_ORG)/$(APP):latest IMAGE := $(REGISTRY)/$(IMAGE_NAME) -CUDA_IMAGE := $(REGISTRY)/$(REGISTRY_ORG)/$(COMPONENT)/$(APP)_cuda:latest -VULKAN_IMAGE := $(REGISTRY)/$(REGISTRY_ORG)/$(COMPONENT)/$(APP)_vulkan:latest +# CUDA_IMAGE := $(REGISTRY)/$(REGISTRY_ORG)/$(APP)_cuda:latest +# VULKAN_IMAGE := $(REGISTRY)/$(REGISTRY_ORG)/$(APP)_vulkan:latest +BIND_MOUNT_OPTIONS := ro +OS := $(shell uname -s) +ifeq ($(OS),Linux) + BIND_MOUNT_OPTIONS := Z,ro +endif -MODEL_NAME ?= facebook/detr-resnet-101 -MODELS_DIR := /models +.PHONY: build +build: + podman build --squash-all --build-arg PORT=$(PORT) -t $(IMAGE) . -f base/Containerfile + +.PHONY: install +install: + pip install -r tests/requirements.txt .PHONY: run run: cd ../../models && \ podman run -it -d -p $(PORT):$(PORT) -v ./$(MODEL_NAME):$(MODELS_DIR)/$(MODEL_NAME):$(BIND_MOUNT_OPTIONS) -e MODEL_PATH=$(MODELS_DIR)/$(MODEL_NAME) -e HOST=0.0.0.0 -e PORT=$(PORT) $(IMAGE) - .PHONY: all all: build download-model-facebook-detr-resnet-101 run .PHONY: download-model-facebook-detr-resnet-101 download-model-facebook-detr-resnet-101: - pip install -r ../../convert_models/requirements.txt - cd ../../convert_models/ && \ - python3.11 download_huggingface.py -m facebook/detr-resnet-101 - cp -r ../../convert_models/converted_models/facebook ../../models/ + cd ../../models && \ + make download-model-facebook-detr-resnet-101 .PHONY: test test: - $(MAKE) download-model-facebook-detr-resnet-101 - ln -s ../../models/detr-resnet-101 ./ - PORT=$(PORT) MODEL_NAME=$(MODEL_NAME) MODELS_PATH=$(MODELS_PATH) IMAGE=$(IMAGE) PULL_ALWAYS=0 pytest -s -vvv + cp -r ../../models/facebook ./ + REGISTRY=$(REGISTRY) MODEL_NAME=$(MODEL_NAME) MODELS_DIR=$(MODELS_DIR) IMAGE_NAME=$(IMAGE_NAME) PORT=$(PORT) pytest -s -vvv diff --git a/model_servers/object_detection_python/base/Containerfile b/model_servers/object_detection_python/base/Containerfile index c788aa14d..0e9d65ef3 100644 --- a/model_servers/object_detection_python/base/Containerfile +++ b/model_servers/object_detection_python/base/Containerfile @@ -1,9 +1,8 @@ FROM registry.access.redhat.com/ubi9/python-311:1-52.1712567218 ARG PORT=8000 WORKDIR /app -COPY src/requirements.txt . +COPY src . RUN pip install --upgrade pip && \ pip install --no-cache-dir --upgrade -r requirements.txt -COPY src/object_detection_server.py . EXPOSE $PORT -ENTRYPOINT [ "uvicorn", "object_detection_server:app", "--host", "0.0.0.0" ] \ No newline at end of file +ENTRYPOINT [ "sh", "./run.sh" ] \ No newline at end of file diff --git a/model_servers/object_detection_python/src/object_detection_server.py b/model_servers/object_detection_python/src/object_detection_server.py index a0e339d54..0f8a1e996 100644 --- a/model_servers/object_detection_python/src/object_detection_server.py +++ b/model_servers/object_detection_python/src/object_detection_server.py @@ -11,7 +11,7 @@ app = FastAPI() -model = os.getenv("MODEL_PATH", default="facebook/detr-resnet-101") +model = os.getenv("MODEL_PATH", default="/app/models/facebook/detr-resnet-101") revision = os.getenv("MODEL_REVISION", default="no_timm") if os.path.isfile(model): @@ -30,6 +30,10 @@ class Item(BaseModel): image: bytes +@app.get("/health") +def tests_alive(): + return {"alive": True} + @app.post("/detection") def detection(item: Item): b64_image = item.image diff --git a/model_servers/object_detection_python/src/run.sh b/model_servers/object_detection_python/src/run.sh new file mode 100644 index 000000000..1b0c0a7a2 --- /dev/null +++ b/model_servers/object_detection_python/src/run.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +if [ ${MODEL_PATH} ]; then + PORT=${PORT} MODEL_PATH=${MODEL_PATH} uvicorn object_detection_server:app --host ${HOST:=0.0.0.0} + exit 0 +fi + +echo "Please set either a MODEL_PATH" +exit 1 + diff --git a/model_servers/object_detection_python/tests/__init__.py b/model_servers/object_detection_python/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/model_servers/object_detection_python/tests/conftest.py b/model_servers/object_detection_python/tests/conftest.py new file mode 100644 index 000000000..ccef043a0 --- /dev/null +++ b/model_servers/object_detection_python/tests/conftest.py @@ -0,0 +1,45 @@ +import pytest_container +import os + +REGISTRY = os.getenv("REGISTRY", "ghcr.io") +IMAGE_NAME = os.getenv("IMAGE_NAME", "containers/object_detection_python:latest") +MODEL_NAME = os.getenv("MODEL_NAME", "facebook/detr-resnet-101") +MODELS_DIR = os.getenv("MODELS_DIR", "/app/models") + +MODEL_PATH = f"{MODELS_DIR}/{MODEL_NAME}" + +PORT = os.getenv("PORT", 8000) +if type(PORT) == str: + try: + PORT = int(PORT) + except: + PORT = 8000 + +MS = pytest_container.Container( + url=f"containers-storage:{REGISTRY}/{IMAGE_NAME}", + volume_mounts=[ + pytest_container.container.BindMount( + container_path=f"{MODEL_PATH}", + host_path=f"./{MODEL_NAME}", + flags=["ro"] + ) + ], + entry_point="auto", + extra_environment_variables={ + "MODEL_PATH": f"{MODEL_PATH}", + "HOST": "0.0.0.0", + "PORT": f"{PORT}" + }, + forwarded_ports=[ + pytest_container.PortForwarding( + container_port=PORT, + host_port=PORT + ) + ], + ) + +def pytest_generate_tests(metafunc): + pytest_container.auto_container_parametrize(metafunc) + +def pytest_addoption(parser): + pytest_container.add_logging_level_options(parser) diff --git a/model_servers/object_detection_python/tests/requirements.txt b/model_servers/object_detection_python/tests/requirements.txt new file mode 100644 index 000000000..22fc97f27 --- /dev/null +++ b/model_servers/object_detection_python/tests/requirements.txt @@ -0,0 +1,8 @@ +pip==24.0 +pytest-container==0.4.0 +pytest-selenium==4.1.0 +pytest-testinfra==10.1.0 +pytest==8.1.1 +requests==2.31.0 +selenium==4.19.0 +tenacity==8.2.3 diff --git a/model_servers/object_detection_python/tests/test_alive.py b/model_servers/object_detection_python/tests/test_alive.py new file mode 100644 index 000000000..6f57d10a6 --- /dev/null +++ b/model_servers/object_detection_python/tests/test_alive.py @@ -0,0 +1,17 @@ +import pytest_container +from .conftest import MS +# import os + +# MODEL_PATH = os.getenv("MODEL_PATH", "/app/models/facebook/detr-resnet-101") + +CONTAINER_IMAGES = [MS] + +def test_etc_os_release_present(auto_container: pytest_container.container.ContainerData): + assert auto_container.connection.file("/etc/os-release").exists + +# def test_model_present(auto_container: pytest_container.container.ContainerData): +# assert auto_container.connection.dir(MODEL_PATH).exists + +def test_alive(auto_container: pytest_container.container.ContainerData, host): + # host.run_expect([0],f"curl http://localhost:{PORT}/health",).stdout.strip() + host.run_expect([0],f"curl http://0.0.0.0:{auto_container.forwarded_ports[0].host_port}/health",).stdout.strip() diff --git a/models/Makefile b/models/Makefile index 92ff4a0d0..53d914e9f 100644 --- a/models/Makefile +++ b/models/Makefile @@ -37,6 +37,13 @@ download-model-mistral: download-model-mistral-code: $(MAKE) MODEL_NAME=mistral-7b-code-16k-qlora.Q4_K_M.gguf MODEL_URL=https://huggingface.co/TheBloke/Mistral-7B-Code-16K-qlora-GGUF/resolve/main/mistral-7b-code-16k-qlora.Q4_K_M.gguf download-model +.PHONY: download-model-facebook-detr-resnet-101 +download-model-facebook-detr-resnet-101: + pip install -r ../convert_models/requirements.txt + cd ../convert_models/ && \ + python3 download_huggingface.py -m facebook/detr-resnet-101 + cp -r ../convert_models/converted_models/facebook ./ + .PHONY: clean clean: -rm -f *tmp diff --git a/recipes/computer_vision/object_detection/client/Containerfile b/recipes/computer_vision/object_detection/app/Containerfile similarity index 100% rename from recipes/computer_vision/object_detection/client/Containerfile rename to recipes/computer_vision/object_detection/app/Containerfile diff --git a/recipes/computer_vision/object_detection/client/object_detection_client.py b/recipes/computer_vision/object_detection/app/object_detection_client.py similarity index 92% rename from recipes/computer_vision/object_detection/client/object_detection_client.py rename to recipes/computer_vision/object_detection/app/object_detection_client.py index e843145f2..5f718604b 100644 --- a/recipes/computer_vision/object_detection/client/object_detection_client.py +++ b/recipes/computer_vision/object_detection/app/object_detection_client.py @@ -6,7 +6,7 @@ import io st.title("🕵️‍♀️ Object Detection") -endpoint =os.getenv("MODEL_ENDPOINT", default = "http://0.0.0.0:8000") +endpoint =os.getenv("MODEL_ENDPOINT", default = "http://0.0.0.0:8000/detection") headers = {"accept": "application/json", "Content-Type": "application/json"} image = st.file_uploader("Upload Image") @@ -27,7 +27,7 @@ img_bytes = bytes_io.getvalue() b64_image = base64.b64encode(img_bytes).decode('utf-8') data = {'image': b64_image} - response = requests.post(f'{endpoint}/detection', headers=headers,json=data, verify=False) + response = requests.post(f'{endpoint}', headers=headers,json=data, verify=False) # parse response and display outputs response_json = response.json() image = response_json["image"] diff --git a/recipes/computer_vision/object_detection/app/requirements.txt b/recipes/computer_vision/object_detection/app/requirements.txt new file mode 100644 index 000000000..f49b86687 --- /dev/null +++ b/recipes/computer_vision/object_detection/app/requirements.txt @@ -0,0 +1,40 @@ +altair==5.3.0 +attrs==23.2.0 +blinker==1.7.0 +cachetools==5.3.3 +certifi==2024.2.2 +charset-normalizer==3.3.2 +click==8.1.7 +gitdb==4.0.11 +GitPython==3.1.43 +idna==3.7 +Jinja2==3.1.3 +jsonschema==4.21.1 +jsonschema-specifications==2023.12.1 +markdown-it-py==3.0.0 +MarkupSafe==2.1.5 +mdurl==0.1.2 +numpy==1.26.4 +packaging==24.0 +pandas==2.2.2 +pillow==10.3.0 +protobuf==4.25.3 +pyarrow==15.0.2 +pydeck==0.8.1b0 +Pygments==2.17.2 +python-dateutil==2.9.0.post0 +pytz==2024.1 +referencing==0.34.0 +requests==2.31.0 +rich==13.7.1 +rpds-py==0.18.0 +six==1.16.0 +smmap==5.0.1 +streamlit==1.33.0 +tenacity==8.2.3 +toml==0.10.2 +toolz==0.12.1 +tornado==6.4 +typing_extensions==4.11.0 +tzdata==2024.1 +urllib3==2.2.1 diff --git a/recipes/computer_vision/object_detection/client/requirements.txt b/recipes/computer_vision/object_detection/client/requirements.txt deleted file mode 100644 index 7b2195a0d..000000000 --- a/recipes/computer_vision/object_detection/client/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -streamlit -requests -pillow \ No newline at end of file diff --git a/recipes/computer_vision/object_detection/tests/__init__.py b/recipes/computer_vision/object_detection/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/recipes/computer_vision/object_detection/tests/conftest.py b/recipes/computer_vision/object_detection/tests/conftest.py new file mode 100644 index 000000000..eebb15a97 --- /dev/null +++ b/recipes/computer_vision/object_detection/tests/conftest.py @@ -0,0 +1,54 @@ +import pytest_container +import pytest +import os +import logging +import platform + +if 'PORT' not in os.environ: + PORT = 8000 +else: + PORT = os.environ['PORT'] + try: + PORT = int(PORT) + except: + PORT = 8000 + +if 'IMAGE' not in os.environ: + IMAGE = 'ghcr.io/containers/model_servers/object_detection_python:latest' +else: + IMAGE = os.environ['IMAGE'] + +MODEL_NAME=os.environ['MODEL_NAME'] +MODEL_PATH=os.environ['MODEL_PATH'] + +BIND_MOUNT_OPTIONS = 'ro' + +MS = pytest_container.Container( + url=f"containers-storage:{IMAGE}", + volume_mounts=[ + pytest_container.container.BindMount( + container_path=f"{MODEL_PATH}", + host_path=f"./{MODEL_NAME}", + flags=[BIND_MOUNT_OPTIONS] + ) + ], + extra_environment_variables={ + "MODEL_NAME": f"{MODEL_NAME}", + "MODEL_PATH": f"{MODEL_PATH}", + "HOST": "0.0.0.0", + "PORT": f"{PORT}" + }, + forwarded_ports=[ + pytest_container.PortForwarding( + container_port=PORT, + host_port=PORT + ) + ], + ) + +def pytest_addoption(parser): + pytest_container.add_logging_level_options(parser) + + +def pytest_generate_tests(metafunc): + pytest_container.auto_container_parametrize(metafunc) diff --git a/recipes/computer_vision/object_detection/tests/requirements.txt b/recipes/computer_vision/object_detection/tests/requirements.txt new file mode 100644 index 000000000..22fc97f27 --- /dev/null +++ b/recipes/computer_vision/object_detection/tests/requirements.txt @@ -0,0 +1,8 @@ +pip==24.0 +pytest-container==0.4.0 +pytest-selenium==4.1.0 +pytest-testinfra==10.1.0 +pytest==8.1.1 +requests==2.31.0 +selenium==4.19.0 +tenacity==8.2.3 diff --git a/recipes/computer_vision/object_detection/tests/test_alive.py b/recipes/computer_vision/object_detection/tests/test_alive.py new file mode 100644 index 000000000..226aac1c0 --- /dev/null +++ b/recipes/computer_vision/object_detection/tests/test_alive.py @@ -0,0 +1,12 @@ +import pytest_container +from .conftest import MS +import tenacity + +CONTAINER_IMAGES = [MS] + +def test_etc_os_release_present(auto_container: pytest_container.container.ContainerData): + assert auto_container.connection.file("/etc/os-release").exists + +@tenacity.retry(stop=tenacity.stop_after_attempt(5), wait=tenacity.wait_exponential()) +def test_alive(auto_container: pytest_container.container.ContainerData, host): + host.run_expect([0],f"curl http://localhost:{auto_container.forwarded_ports[0].host_port}",).stdout.strip()