├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── compose.yaml ├── data ├── cats.jpeg ├── palace.jpeg └── tiger.jpeg ├── main.py ├── model.py ├── model_starter.py └── requirements.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | # Include any files or directories that you don't want to be copied to your 2 | # container here (e.g., local build artifacts, temporary files, etc.). 3 | # 4 | # For more help, visit the .dockerignore file reference guide at 5 | # https://docs.docker.com/engine/reference/builder/#dockerignore-file 6 | 7 | **/.DS_Store 8 | **/__pycache__ 9 | **/.venv 10 | **/venv 11 | **/.classpath 12 | **/.dockerignore 13 | **/.env 14 | **/.git 15 | **/.gitignore 16 | **/.project 17 | **/.settings 18 | **/.toolstarget 19 | **/.vs 20 | **/.vscode 21 | **/*.*proj.user 22 | **/*.dbmdl 23 | **/*.jfm 24 | **/bin 25 | **/charts 26 | **/docker-compose* 27 | **/compose* 28 | **/Dockerfile* 29 | **/node_modules 30 | **/npm-debug.log 31 | **/obj 32 | **/secrets.dev.yaml 33 | **/values.dev.yaml 34 | LICENSE 35 | README.md 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /venv 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | # Comments are provided throughout this file to help you get started. 4 | # If you need more help, visit the Dockerfile reference guide at 5 | # https://docs.docker.com/engine/reference/builder/ 6 | 7 | ARG PYTHON_VERSION=3.10.5 8 | FROM python:${PYTHON_VERSION} as base 9 | 10 | # Install Rust compiler 11 | RUN curl https://sh.rustup.rs -sSf | bash -s -- -y 12 | ENV PATH="/root/.cargo/bin:${PATH}" 13 | 14 | # Prevents Python from writing pyc files. 15 | ENV PYTHONDONTWRITEBYTECODE=1 16 | 17 | # Keeps Python from buffering stdout and stderr to avoid situations where 18 | # the application crashes without emitting any logs due to buffering. 19 | ENV PYTHONUNBUFFERED=1 20 | 21 | WORKDIR /app 22 | 23 | # Download dependencies as a separate step to take advantage of Docker's caching. 24 | # Leverage a cache mount to /root/.cache/pip to speed up subsequent builds. 25 | # Leverage a bind mount to requirements.txt to avoid having to copy them into 26 | # into this layer. 27 | RUN --mount=type=cache,target=/root/.cache/pip \ 28 | --mount=type=bind,source=requirements.txt,target=requirements.txt \ 29 | python -m pip install -r requirements.txt 30 | 31 | # Copy the source code into the container. 32 | COPY . . 33 | 34 | # Expose the port that the application listens on. 35 | EXPOSE 8000 36 | 37 | # Run the application. 38 | CMD uvicorn main:app --reload --host 0.0.0.0 --port 8000 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An example app that shows how to build a ML app with FastAPI and Docker. 2 | 3 | The starter file is [model_starter.py](model_starter.py) with a model from Hugging Face: https://huggingface.co/dandelin/vilt-b32-finetuned-vqa 4 | 5 | The final app is in [main.py](main.py) and final model code is in [model.py](model.py). 6 | 7 | Below are the steps to use Docker: 8 | 9 | ## Steps to Setup Docker and VS Code 10 | 11 | STEP: Run `docker init` 12 | 13 | ``` 14 | docker init 15 | ``` 16 | 17 | STEP: Select Python image and use this command during docker init 18 | ``` 19 | CMD uvicorn main:app --reload --host 0.0.0.0 --port 8000 20 | ``` 21 | 22 | STEP: Remove `-slim` and add this to Dockerfile (Needed to install transformers) 23 | 24 | ``` 25 | # Install Rust compiler 26 | RUN curl https://sh.rustup.rs -sSf | bash -s -- -y 27 | ENV PATH="/root/.cargo/bin:${PATH}" 28 | ``` 29 | 30 | STEP: Remove the custom user from the Dockerfile to simplify, because HF needs write permissions to download and save the model. 31 | 32 | STEP: Add volume to `compose.yaml`: 33 | 34 | ``` 35 | volumes: 36 | - .:/app 37 | ``` 38 | 39 | STEP Run `docker compose up --build` 40 | 41 | STEP: Attach VS Code to running container 42 | 43 | ## Examples 44 | 45 | - file: cats.jpeg 46 | 47 | text: How many cats are there? 48 | 49 | - file: tiger.jpeg 50 | 51 | text: What's the animal doing? 52 | 53 | - file: palace.jpeg 54 | 55 | text: What is on top of the building? -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | # Comments are provided throughout this file to help you get started. 2 | # If you need more help, visit the Docker compose reference guide at 3 | # https://docs.docker.com/compose/compose-file/ 4 | 5 | # Here the instructions define your application as a service called "server". 6 | # This service is built from the Dockerfile in the current directory. 7 | # You can add other services your application may depend on here, such as a 8 | # database or a cache. For examples, see the Awesome Compose repository: 9 | # https://github.com/docker/awesome-compose 10 | services: 11 | server: 12 | build: 13 | context: . 14 | ports: 15 | - 8000:8000 16 | volumes: 17 | - .:/app 18 | 19 | # The commented out section below is an example of how to define a PostgreSQL 20 | # database that your application can use. `depends_on` tells Docker Compose to 21 | # start the database before your application. The `db-data` volume persists the 22 | # database data between container restarts. The `db-password` secret is used 23 | # to set the database password. You must create `db/password.txt` and add 24 | # a password of your choosing to it before running `docker compose up`. 25 | # depends_on: 26 | # db: 27 | # condition: service_healthy 28 | # db: 29 | # image: postgres 30 | # restart: always 31 | # user: postgres 32 | # secrets: 33 | # - db-password 34 | # volumes: 35 | # - db-data:/var/lib/postgresql/data 36 | # environment: 37 | # - POSTGRES_DB=example 38 | # - POSTGRES_PASSWORD_FILE=/run/secrets/db-password 39 | # expose: 40 | # - 5432 41 | # healthcheck: 42 | # test: [ "CMD", "pg_isready" ] 43 | # interval: 10s 44 | # timeout: 5s 45 | # retries: 5 46 | # volumes: 47 | # db-data: 48 | # secrets: 49 | # db-password: 50 | # file: db/password.txt 51 | 52 | -------------------------------------------------------------------------------- /data/cats.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickloeber/docker-fastapi-ml/b20e577d4580d5328d94f35493c96bd8dbb9cfcc/data/cats.jpeg -------------------------------------------------------------------------------- /data/palace.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickloeber/docker-fastapi-ml/b20e577d4580d5328d94f35493c96bd8dbb9cfcc/data/palace.jpeg -------------------------------------------------------------------------------- /data/tiger.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickloeber/docker-fastapi-ml/b20e577d4580d5328d94f35493c96bd8dbb9cfcc/data/tiger.jpeg -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from model import model_pipeline 2 | 3 | from typing import Union 4 | 5 | from fastapi import FastAPI, UploadFile 6 | import io 7 | from PIL import Image 8 | 9 | app = FastAPI() 10 | 11 | 12 | @app.get("/") 13 | def read_root(): 14 | return {"Hello": "World"} 15 | 16 | 17 | @app.post("/ask") 18 | def ask(text: str, image: UploadFile): 19 | content = image.file.read() 20 | 21 | image = Image.open(io.BytesIO(content)) 22 | # image = Image.open(image.file) 23 | 24 | result = model_pipeline(text, image) 25 | return {"answer": result} 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /model.py: -------------------------------------------------------------------------------- 1 | from transformers import ViltProcessor, ViltForQuestionAnswering 2 | from PIL import Image 3 | 4 | # 470MB 5 | processor = ViltProcessor.from_pretrained("dandelin/vilt-b32-finetuned-vqa") 6 | model = ViltForQuestionAnswering.from_pretrained("dandelin/vilt-b32-finetuned-vqa") 7 | 8 | 9 | def model_pipeline(text: str, image: Image): 10 | # prepare inputs 11 | encoding = processor(image, text, return_tensors="pt") 12 | 13 | # forward pass 14 | outputs = model(**encoding) 15 | logits = outputs.logits 16 | idx = logits.argmax(-1).item() 17 | 18 | return model.config.id2label[idx] -------------------------------------------------------------------------------- /model_starter.py: -------------------------------------------------------------------------------- 1 | from transformers import ViltProcessor, ViltForQuestionAnswering 2 | import requests 3 | from PIL import Image 4 | 5 | # 470MB 6 | processor = ViltProcessor.from_pretrained("dandelin/vilt-b32-finetuned-vqa") 7 | model = ViltForQuestionAnswering.from_pretrained("dandelin/vilt-b32-finetuned-vqa") 8 | 9 | # prepare image + question 10 | url = "http://images.cocodataset.org/val2017/000000039769.jpg" 11 | image = Image.open(requests.get(url, stream=True).raw) 12 | 13 | text = "How many cats are there?" 14 | 15 | # prepare inputs 16 | encoding = processor(image, text, return_tensors="pt") 17 | 18 | # TODO: inspect encoding 19 | 20 | # forward pass 21 | outputs = model(**encoding) 22 | logits = outputs.logits 23 | idx = logits.argmax(-1).item() 24 | 25 | # TODO: inspect outputs and test how to get the answer 26 | #print(idx) 27 | #print() 28 | #print(model.config.id2label)) 29 | 30 | print("Predicted answer:", model.config.id2label[idx]) 31 | 32 | # TODO: put above code into a function that accepts image and text as input -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | Pillow 3 | python-multipart 4 | torch 5 | transformers 6 | uvicorn --------------------------------------------------------------------------------