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

HW9 PR5 #35

Merged
merged 11 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
46 changes: 46 additions & 0 deletions .github/workflows/test_fastapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Run FastApi Tests

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'

- name: Set up AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt # Ensure you have this file in the specified directory
pip install awscli httpx python-multipart
working-directory: serving/fastapi_server

- name: Run AWS S3 Copy Command
run: |
aws s3 cp s3://mlp-data-2024/rtdetr_model/ ./rtdetr_model --recursive
working-directory: serving/gradio_server

- name: Run tests
run: |
python -m pytest tests # This will run all tests in the specified directory
working-directory: serving/fastapi_server
46 changes: 46 additions & 0 deletions .github/workflows/test_gradio.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Run FastApi Tests

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'

- name: Set up AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt # Ensure you have this file in the specified directory
pip install awscli
working-directory: serving/gradio_server

- name: Run AWS S3 Copy Command
run: |
aws s3 cp s3://mlp-data-2024/rtdetr_model/ ./rtdetr_model --recursive
working-directory: serving/gradio_server

- name: Run tests
run: |
python -m pytest tests # This will run all tests in the specified directory
working-directory: serving/gradio_server
45 changes: 45 additions & 0 deletions .github/workflows/test_streamlit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Run FastApi Tests

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'

- name: Set up AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt # Ensure you have this file in the specified directory
pip install awscli
working-directory: serving/streamlit_server

- name: Run AWS S3 Copy Command
run: |
aws s3 cp s3://mlp-data-2024/rtdetr_model/ ./rtdetr_model --recursive

- name: Run tests
run: |
python -m pytest tests # This will run all tests in the specified directory
working-directory: serving/streamlit_server
100 changes: 100 additions & 0 deletions serving/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Streamlit


## Local Deployment
```bash
streamlit run streamlit_ui.py -- --model_path path/to/model
```


## Build Container
```bash
docker build \
--build-arg AWS_ACCESS_KEY_ID=key \
--build-arg AWS_SECRET_ACCESS_KEY="secret_key" \
-t streamlit_app:latest .
```

Run:
```bash
docker run -it --rm -p 8501:8501 streamlit_app:latest
```

## Kubernetes Deployment

Add secrets to the cluster:
```bash
kubectl create secret generic aws-secret \
--from-literal=aws_access_key_id=key \
--from-literal=aws_secret_access_key="secret_key"
```

```bash
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl port-forward <pod_name> 8000:8000
```
And then go to `localhost:8501`.

# Gradio

## Local Deployment
```bash
python gradio_ui.py --model_path path/to/model
```

## Build Container
```bash
docker build \
--build-arg AWS_ACCESS_KEY_ID=key \
--build-arg AWS_SECRET_ACCESS_KEY="secret_key" \
-t gradio_app:latest .
```

Run:
```bash
docker run -it --rm -p 7860:7860 gradio_app:latest
```


# FastAPI
## Local Deployment
```bash
pip install -r requirements.txt
python fastapi_server.py
```

## Build Container
```bash
docker build \
--build-arg AWS_ACCESS_KEY_ID=key \
--build-arg AWS_SECRET_ACCESS_KEY="secret_key" \
-t fastapi_app:latest .
```

Run:
```bash
docker run -it --rm -p 8000:8000 fastapi_app:latest
```

How to make a request:
```bash
curl -X POST "http://localhost:8000/predict/" -F "image=@/path/to/image.jpg" -F "threshold=0.5"
```


## Kubernetes Deployment

Add secrets to the cluster:
```bash
kubectl create secret generic aws-secret \
--from-literal=aws_access_key_id=key \
--from-literal=aws_secret_access_key="secret_key"
```

```bash
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl port-forward <pod_name> 8000:8000
```
And then use API as described above.
22 changes: 22 additions & 0 deletions serving/fastapi_server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt requirements.txt

RUN pip install -r requirements.txt
RUN pip install awscli
RUN pip install python-multipart

COPY . /app

EXPOSE 8000

ARG AWS_SECRET_ACCESS_KEY
ARG AWS_ACCESS_KEY_ID
ENV AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
ENV AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}

RUN aws s3 cp s3://mlp-data-2024/rtdetr_model/ ./rtdetr_model --recursive

CMD ["python", "fastapi_server.py"]
77 changes: 77 additions & 0 deletions serving/fastapi_server/fastapi_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import os
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import JSONResponse
from PIL import Image
import torch
from transformers import AutoModelForObjectDetection, AutoImageProcessor
from io import BytesIO
from http import HTTPStatus
from typing import Dict

from fastapi import HTTPException
from PIL import UnidentifiedImageError

device = torch.device("mps" if torch.backends.mps.is_available() else "cpu")

app = FastAPI()


model_path = "./rtdetr_model"

try:
model = AutoModelForObjectDetection.from_pretrained(model_path).to(device).eval()
processor = AutoImageProcessor.from_pretrained(model_path)
model_loaded = True
except Exception as e:
model_loaded = False
print(f"Error loading model: {e}")

MODEL_LABEL_MAPPING = {0: "person", 1: "car", 2: "pet"}

@app.get("/")
def _index() -> Dict:
"""Health check."""
response = {
"message": HTTPStatus.OK.phrase,
"status_code": HTTPStatus.OK,
"data": {"model_loaded": model_loaded},
}
return response


def predict(image: Image.Image, threshold: float):
inputs = processor(images=image, return_tensors="pt")
inputs = {k: v.to(device) for k, v in inputs.items()}

outputs = model(**inputs)

target_sizes = torch.tensor([image.size[::-1]]) # target size in (height, width)
results = processor.post_process_object_detection(outputs, target_sizes=target_sizes, threshold=threshold)[0]
results = {k: v.detach().cpu() for k, v in results.items()}

return results

@app.post("/predict/")
async def inference(image: UploadFile = File(...), threshold: float = 0.5):
try:
image_data = await image.read()
image = Image.open(BytesIO(image_data))
except UnidentifiedImageError:
raise HTTPException(status_code=400, detail="Invalid image file")

results = predict(image, threshold)

output_data = []
for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
output_data.append({
"class": MODEL_LABEL_MAPPING[label.item()],
"score": score.item(),
"box": [box[0].item(), box[1].item(), box[2].item(), box[3].item()]
})

return JSONResponse(content={"predictions": output_data})


if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
39 changes: 39 additions & 0 deletions serving/fastapi_server/k8s/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastapi-deployment
labels:
app: fastapi
spec:
replicas: 1
selector:
matchLabels:
app: fastapi
template:
metadata:
labels:
app: fastapi
spec:
containers:
- name: fastapi-container
image: alexuvarovskii/fastapi_app:latest
ports:
- containerPort: 8000
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-secret
key: aws_access_key_id
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aws-secret
key: aws_secret_access_key
resources:
requests:
memory: "4096Mi"
cpu: "2"
limits:
memory: "10Gi"
cpu: "4"
11 changes: 11 additions & 0 deletions serving/fastapi_server/k8s/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: fastapi-service
spec:
type: LoadBalancer
ports:
- port: 8000
targetPort: 8000
selector:
app: fastapi
Loading
Loading