Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creation of services in OSCAR #7

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#multi-platform execution of services
.ONESHELL:
SHELL := /bin/bash

Expand Down Expand Up @@ -28,7 +29,9 @@ build-push-dimensionality-reduction:
docker buildx build --platform linux/amd64,linux/arm64 -t ifcacomputing/dimensionality-reduction-api --push dimensionality_reduction_api

build-push-model-inference:
docker buildx build --platform linux/amd64,linux/arm64 -t ifcacomputing/model-inference-api:latest --push model_inference_api
docker build -t ghcr.io/grycap/mls-arm-api model_inference_api
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 Why do you change the registry where this image is stored?

docker push ghcr.io/grycap/mls-arm-api
docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/grycap/mls-arm-api --push model_inference_api

run-data-drift-detector:
docker run --name data-drift-detection -p 5001:8000 ifcacomputing/data-drift-detection-api
Expand All @@ -37,4 +40,24 @@ run-dimensionality-reduction:
docker run --name dimensionality-reduction -p 5002:8000 ifcacomputing/dimensionality-reduction-api

run-model-inference:
docker run --name model-inference -p 5003:8000 ifcacomputing/model-inference-api
docker run --name model-inference -p 5003:8000 ifcacomputing/model-inference-api

mls:
docker build -t ghcr.io/grycap/mls-arm-api model_inference_api
docker push ghcr.io/grycap/mls-arm-api
docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/grycap/mls-arm-api --push model_inference_api

dds:
docker build -t ghcr.io/grycap/dds-arm-api detector_api
docker push ghcr.io/grycap/dds-arm-api
docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/grycap/dds-arm-api --push detector_api

emc:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 I think that this should be the unique "new" service. The addition of the others (mls, dds and drs) is completely redundant. Because they are already hosted in ifcacomputing/XXXX-api.

Copy link
Author

@vicente87 vicente87 Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaime-cespedes-sisniega The problem is that in order to make the asynchronous invocations we create main-service.py (it's from what was put in main.py) and there we introduce small changes to be able to orchestrate the services well among others. For example we create output files in the drs services in json format so that you can use it well. Also, the input data that comes from the MinIO buckets has to accommodate the functions that you have created and that is why we decided to create the new images. I don't know if I will explain well.

docker build -t ghcr.io/grycap/emc-arm-api embedding_matrix
docker push ghcr.io/grycap/emc-arm-api
docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/grycap/emc-arm-api --push embedding_matrix
drs:
docker build -t ghcr.io/grycap/drs-arm-api dimensionality_reduction_api
docker push ghcr.io/grycap/drs-arm-api
docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/grycap/drs-arm-api --push dimensionality_reduction_api

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 This file is empty.

Copy link
Author

@vicente87 vicente87 Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaime-cespedes-sisniega You are right, this file does not have to be there.

Empty file.
30 changes: 30 additions & 0 deletions creation_service/ML_inference/ml_inference.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
functions:
oscar:
- oscar-cluster:
name: mls-service
memory: 2Gi
cpu: '2'
image: ghcr.io/grycap/mls-arm-api
script: script.sh
log_level: INFO
input:
- storage_provider: minio.default
path: images/input
output:
- suffix:
- txt
storage_provider: minio.default
path: mls/output
- suffix:
- png
storage_provider: minio.minio1
path: drs/input

storage_providers:
minio:
minio1:
endpoint: 'https://minio.frosty-grothendieck5.im.grycap.net'
region: us-east-1
access_key: minio
secret_key: minio123
verify: false
4 changes: 4 additions & 0 deletions creation_service/ML_inference/script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FILE_NAME=`basename "$INPUT_FILE_PATH"`
OUTPUT_FILE="$TMP_OUTPUT_DIR/$FILE_NAME"
cp $INPUT_FILE_PATH $OUTPUT_FILE
python3 main-service.py $INPUT_FILE_PATH $OUTPUT_FILE
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 This file is empty.

Copy link
Author

@vicente87 vicente87 Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaime-cespedes-sisniega You are right, this file does not have to be there.

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
functions:
oscar:
- oscar-cluster1:
name: drs-service
memory: 2Gi
cpu: '2'
image: ghcr.io/grycap/drs-arm-api
script: script.sh
log_level: INFO
input:
- storage_provider: minio
path: drs/input
output:
- storage_provider: minio
path: drs/output

4 changes: 4 additions & 0 deletions creation_service/dimensionality_reduction/script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FILE_NAME=`basename "$INPUT_FILE_PATH"`
OUTPUT_FILE="$TMP_OUTPUT_DIR/$FILE_NAME"

python3 main-service.py $INPUT_FILE_PATH $OUTPUT_FILE
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 This file is empty.

Copy link
Author

@vicente87 vicente87 Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaime-cespedes-sisniega You are right, this file does not have to be there.

Empty file.
25 changes: 25 additions & 0 deletions creation_service/drift_detection/drift_detection.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
functions:
oscar:
- oscar-cluster1:
name: dds-service
memory: 2Gi
cpu: '2'
image: ghcr.io/grycap/dds-arm-api
script: script.sh
log_level: INFO
input:
- storage_provider: minio
path: emc/output
output:
- storage_provider: minio.minio-rasp
path: dds/output
- storage_provider: minio.default
path: dds/output
storage_providers:
minio:
minio-rasp:
endpoint: 'https://minio.graspi.im.grycap.net'
region: us-east-1
access_key: minio
secret_key: minio-aisprint-2021
verify: false
3 changes: 3 additions & 0 deletions creation_service/drift_detection/script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FILE_NAME=`basename "$INPUT_FILE_PATH"`
OUTPUT_FILE="$TMP_OUTPUT_DIR/$FILE_NAME"
python3 main-service.py $INPUT_FILE_PATH $OUTPUT_FILE
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 This file is empty.

Copy link
Author

@vicente87 vicente87 Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaime-cespedes-sisniega You are right, this file does not have to be there.

Empty file.
15 changes: 15 additions & 0 deletions creation_service/embedding_matrix/embedding_matrix.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
functions:
oscar:
- oscar-cluster1:
name: emc-service
memory: 2Gi
cpu: '2'
image: ghcr.io/grycap/emc-arm-api
script: script.sh
log_level: INFO
input:
- storage_provider: minio
path: drs/output
output:
- storage_provider: minio
path: emc/output
3 changes: 3 additions & 0 deletions creation_service/embedding_matrix/script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FILE_NAME=`basename "$INPUT_FILE_PATH"`
OUTPUT_FILE="$TMP_OUTPUT_DIR/$FILE_NAME"
python3 main-service.py $INPUT_FILE_PATH $OUTPUT_FILE
Binary file modified detector_api/app/detector/detector.pkl
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 This file must not be modified. It is generated by the training.py script.

Binary file not shown.
68 changes: 68 additions & 0 deletions detector_api/app/main-service.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 Why do you create this main-service.py when there is a main.py?

Copy link
Author

@vicente87 vicente87 Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaime-cespedes-sisniega The main-service.py is the modification of the main.py created by you, where the functions created have been reused to accommodate them to the invocation of MinIO services. Here output files are created so that they can be used by subsequent services and then conform to the functions created by you. But basically what the main-service.py (of the MLIS, DRS and DDS services) have is to accommodate the inputs and outputs of the files left in the MinIO buckets to be used by the previously created functions. Which perhaps could have been called main.py to follow the same structure that you created.

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""API module."""
# main-service.py for execute DDS (Drift Detection Service) service.

import logging
import sys
import json
import numpy as np
from detector import Detector
from litestar import Router, get, post
from litestar.status_codes import (
HTTP_200_OK,
)
from schemas import (
DetectorInputData,
DistanceBasedResponse,
HealthResponse,
)
from settings import api_settings, detector_settings

detector = Detector(
settings=detector_settings,
)

class Variable:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this Variable class?

Copy link
Author

@vicente87 vicente87 Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaime-cespedes-sisniega That was a mistake that stuck with us.

def __init__(self, d1,d2,d3):
self.alpha = d1
self.values = d2
self.return_input_values=d3


def check_drift(data: DetectorInputData,out) -> DistanceBasedResponse:
#async
"""Check if drift is present.

:param data: input data
:type data: DetectorInputData
:return: drift data information
:rtype: BaseCheckDriftResponse
"""


logging.info("Checking drift...")
check_drift_result = detector.check_drift(
#The value parameter has to be in an array with the N embedding
values=np.array(data['values']),
alpha=data['alpha'],
)
return_input_values=data['return_input_values']
value=np.array(data['values'])
check_drift_result["distance"] = check_drift_result["distance"].distance
if return_input_values:
check_drift_result["values"] = value.tolist() # noqa: PD011
result = DistanceBasedResponse(
**check_drift_result,
)
#Save the results of the algorithm in the output bucket (dds/output)
f = open(str(out)+".txt", "a")
f.write(str(result))
f.close()

#Read json object with data from EMC service
with open(sys.argv[1]) as f:
data = json.load(f)
print(data)

#Pass parameters (data and output bucket) to the drift detection algorithm
check_drift(data,sys.argv[2])

3 changes: 2 additions & 1 deletion dimensionality_reduction_api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ RUN apt-get -y update && \
adduser --no-create-home --shell /bin/false --disabled-password --uid 1001 --system --group app && \
pip install --upgrade -r requirements.txt --no-cache-dir && \
rm requirements.txt
RUN pip install Pillow
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 Why do you need Pillow?

Copy link
Author

@vicente87 vicente87 Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaime-cespedes-sisniega Here that stuck to us because we were using it to manipulate images and later we saw that we could solve it with PIL.
Also, at that time I had not realized that the libraries could be installed by putting them in the requirements.txt file.


COPY app ./

USER app

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
51 changes: 51 additions & 0 deletions dimensionality_reduction_api/app/main-service.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 Why do you create this main-service.py when there is a main.py?

Copy link
Author

@vicente87 vicente87 Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaime-cespedes-sisniega The main-service.py is the modification of the main.py created by you, where the functions created have been reused to accommodate them to the invocation of MinIO services. Here output files are created so that they can be used by subsequent services and then conform to the functions created by you. But basically what the main-service.py (of the MLIS, DRS and DDS services) have is to accommodate the inputs and outputs of the files left in the MinIO buckets to be used by the previously created functions. Which perhaps could have been called main.py to follow the same structure that you created.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# main-service.py for execute DRS (Demensionality Reduction Service) service.

import logging
import sys
import numpy as np
from PIL import Image
from litestar import Router, get, post
from litestar.enums import RequestEncodingType
from litestar.params import Body
from litestar.status_codes import (
HTTP_200_OK,
)

from dr import DimensionalityReduction
from schemas import (
DimensionalityReductionInputData,
DimensionalityReductionResponse,
HealthResponse,
)
from settings import (
api_settings,
encoder_settings,
transformer_settings,
)

dr = DimensionalityReduction(
settings_encoder=encoder_settings,
settings_transformer=transformer_settings,
)

def dim_red(image, out):
logging.info("Transforming image...")
transformed_image = dr.transform(
data=image,
)
logging.info("Image transformed.")
logging.info("Encoding image...")
reduced_image = dr.encode(
data=transformed_image,
)
logging.info("Image encoded.")

#Write algorithm results to output bucket
f = open(str(out)+".json", "a")
f.write(str(np.squeeze(reduced_image).tolist()))
f.close()

#Capture the image from the input bucket (upload/input) and pass as parameters to the dimensionality
#reduction function the content of the image and the output bucket to put the results of the algorithm
img = Image.open(sys.argv[1])
dim_red(img,sys.argv[2])
Binary file modified dimensionality_reduction_api/app/objects/encoder.pt
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 This file must not be modified. It is generated by the training.py script.

Binary file not shown.
Binary file modified dimensionality_reduction_api/app/objects/transformer.pt
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 This file must not be modified. It is generated by the training.py script.

Binary file not shown.
20 changes: 20 additions & 0 deletions embedding_matrix_api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.10-slim
LABEL author="Vicente Rodriguez Benitez <[email protected]>"

WORKDIR /app

COPY requirements/requirements.txt .

RUN apt-get -y update && \
addgroup --gid 1001 --system app && \
adduser --no-create-home --shell /bin/false --disabled-password --uid 1001 --system --group app && \
pip install --upgrade -r requirements.txt --no-cache-dir && \
rm requirements.txt



COPY app ./

USER app

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
1 change: 1 addition & 0 deletions embedding_matrix_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Detector API __init__."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 It is not the Detector API.

Copy link
Author

@vicente87 vicente87 Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaime-cespedes-sisniega That was a mistake that stuck with us but it has already been revised

1 change: 1 addition & 0 deletions embedding_matrix_api/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""App __init__."""
85 changes: 85 additions & 0 deletions embedding_matrix_api/app/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""API module."""

import logging

from litestar import Router, get, post
from litestar.enums import RequestEncodingType
from litestar.params import Body
from litestar.status_codes import (
HTTP_200_OK,
)

from model import Model
from schemas import (
HealthResponse,
ModelInputData,
PredictResponse,
)
from settings import (
api_settings,
model_settings,
transformer_settings,
)

model = Model(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 Why do you use the Model into the embedding matrix service?

Copy link
Author

@vicente87 vicente87 Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaime-cespedes-sisniega Here we do not use it but as I told you before that we had taken the pattern of services that you gave us and we did not modify many things and that is why they may not be used

settings_model=model_settings,
settings_transformer=transformer_settings,
)


@post(
path="/predict",
status_code=HTTP_200_OK,
)
async def predict(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vicente87 Same here. Embedding matrix should not perform any prediction.

Copy link
Author

@vicente87 vicente87 Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaime-cespedes-sisniega Here we do not use it but as I told you before that we had taken the pattern of services that you gave us and we did not modify many things and that is why they may not be used

data: ModelInputData = Body(
media_type=RequestEncodingType.MULTI_PART,
),
) -> PredictResponse:
"""Predict function.

:param data: model input data
:type data: ModelInputData
:return: prediction response
:rtype: BasePredictionResponse
"""
image = await data.image
logging.info("Transforming image...")
transformed_image = model.transform(
data=image,
)
logging.info("Image transformed.")
logging.info("Predicting...")
prediction = model.predict(
data=transformed_image,
)
logging.info("Prediction made.")
return PredictResponse(
**prediction,
)


@get(
path="/health",
status_code=HTTP_200_OK,
)
async def health() -> HealthResponse:
"""Health check function.

:return: Health check response
:rtype: HealthResponse
"""
return HealthResponse(
name=api_settings.PROJECT_NAME,
api_version=api_settings.VERSION,
)


api_router = Router(
path="/api",
tags=["API"],
route_handlers=[
predict,
health,
],
)
Loading