diff --git a/.gitignore b/.gitignore index 3e152bb4..70dbcf61 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ build/ dist/ +docker/**/*.yml htmlcov/ *egg-info/ __pycache__/ diff --git a/docker/api.Dockerfile b/docker/api.Dockerfile deleted file mode 100644 index 9adab8c5..00000000 --- a/docker/api.Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -FROM nvidia/cuda:10.2-base-ubuntu18.04 -LABEL maintainer="NeuML" -LABEL repository="txtai" - -# Locale environment variables -ENV LC_ALL=C.UTF-8 -ENV LANG=C.UTF-8 - -# Expose default uvicorn port -EXPOSE 8000 - -# Install required packages -RUN apt-get update && \ - apt-get -y --no-install-recommends install libgomp1 libsndfile1 gcc g++ python3.7 python3.7-dev python3-pip && \ - rm -rf /var/lib/apt/lists - -# Install txtai project and dependencies -RUN ln -s /usr/bin/python3.7 /usr/bin/python && \ - python -m pip install --no-cache-dir -U pip wheel setuptools && \ - python -m pip install --no-cache-dir txtai[api,pipeline] && \ - python -c "import nltk; nltk.download('punkt')" - -# Cleanup build packages -RUN apt-get -y purge gcc g++ python3.7-dev && apt-get -y autoremove - -# Generate YAML file -RUN echo "path: /txtai" > index.yml && \ - echo "writable: True" >> index.yml && \ - echo "embeddings:" >> index.yml && \ - echo " method: transformers" >> index.yml && \ - echo " path: sentence-transformers/nli-mpnet-base-v2" >> index.yml && \ - echo "extractor:" >> index.yml && \ - echo " path: distilbert-base-cased-distilled-squad" >> index.yml && \ - echo "labels:" >> index.yml - -# Cache models in Docker image -RUN python -c "from transformers import AutoModel; AutoModel.from_pretrained('sentence-transformers/nli-mpnet-base-v2')" && \ - python -c "from transformers import AutoModel; AutoModel.from_pretrained('distilbert-base-cased-distilled-squad')" && \ - python -c "from transformers import AutoModel; AutoModel.from_pretrained('facebook/bart-large-mnli')" - -# Start script on all interfaces (localhost + bridged interface) -ENTRYPOINT CONFIG=index.yml uvicorn --host 0.0.0.0 "txtai.api:app" diff --git a/docker/api/Dockerfile b/docker/api/Dockerfile new file mode 100644 index 00000000..13dced0c --- /dev/null +++ b/docker/api/Dockerfile @@ -0,0 +1,13 @@ +# Set base image +ARG BASE_IMAGE=neuml/txtai-cpu +FROM $BASE_IMAGE + +# Copy configuration +COPY config.yml . + +# Run local API instance to cache models in container +RUN python -c "from txtai.api import API; API('config.yml')" + +# Start server and listen on all interfaces +ENV CONFIG "config.yml" +ENTRYPOINT ["uvicorn", "--host", "0.0.0.0", "txtai.api:app"] diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile new file mode 100644 index 00000000..86e24ac1 --- /dev/null +++ b/docker/base/Dockerfile @@ -0,0 +1,35 @@ +# Set base image +ARG BASE_IMAGE=ubuntu:20.04 +FROM $BASE_IMAGE + +# Install GPU-enabled version of PyTorch if set +ARG GPU + +# Set Python version (i.e. 3, 3.7, 3.8) +ARG PYTHON_VERSION=3 + +# List of txtai components to install +ARG COMPONENTS=[all] + +# Locale environment variables +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +RUN \ + # Install required packages + apt-get update && \ + apt-get -y --no-install-recommends install libgomp1 libsndfile1 gcc g++ python${PYTHON_VERSION} python${PYTHON_VERSION}-dev python3-pip && \ + rm -rf /var/lib/apt/lists && \ + \ + # Install txtai project and dependencies + ln -s /usr/bin/python${PYTHON_VERSION} /usr/bin/python && \ + python -m pip install --no-cache-dir -U pip wheel setuptools && \ + if [ -z ${GPU} ]; then pip install --no-cache-dir pip install torch==1.10.2+cpu torchvision==0.11.3+cpu -f https://download.pytorch.org/whl/torch_stable.html; fi && \ + python -m pip install --no-cache-dir txtai${COMPONENTS} && \ + python -c "import sys, importlib.util as util; 1 if util.find_spec('nltk') else sys.exit(); import nltk; nltk.download('punkt')" && \ + \ + # Cleanup build packages + apt-get -y purge gcc g++ python${PYTHON_VERSION}-dev && apt-get -y autoremove + +# Set default working directory +WORKDIR /app diff --git a/docker/schedule/Dockerfile b/docker/schedule/Dockerfile new file mode 100644 index 00000000..d48f3b8f --- /dev/null +++ b/docker/schedule/Dockerfile @@ -0,0 +1,12 @@ +# Set base image +ARG BASE_IMAGE=neuml/txtai-cpu +FROM $BASE_IMAGE + +# Copy configuration +COPY config.yml . + +# Run local API instance to cache models in container +RUN python -c "from txtai.api import API; API('config.yml')" + +# Start application and wait for completion. Scheduled workflows can run indefinitely. +ENTRYPOINT ["python", "-c", "from txtai.api import API; API('config.yml').wait()"] diff --git a/docker/workflow/Dockerfile b/docker/workflow/Dockerfile new file mode 100644 index 00000000..3b23b2b7 --- /dev/null +++ b/docker/workflow/Dockerfile @@ -0,0 +1,13 @@ +# Set base image +ARG BASE_IMAGE=neuml/txtai-cpu +FROM $BASE_IMAGE + +# Copy configuration +COPY config.yml . + +# Run local API instance to cache models in container +RUN python -c "from txtai.api import API; API('config.yml')" + +# Run workflow. Requires two command line arguments: name of workflow and input elements +ENTRYPOINT ["python", "-c", "import sys; from txtai.api import API\nfor _ in API('config.yml').workflow(sys.argv[1], sys.argv[2:]): pass"] +CMD ["workflow"] diff --git a/docs/cloud.md b/docs/cloud.md new file mode 100644 index 00000000..02155fbc --- /dev/null +++ b/docs/cloud.md @@ -0,0 +1,83 @@ +# Cloud + +![cloud](images/cloud.png#only-light) +![cloud](images/cloud-dark.png#only-dark) + +Scalable cloud-native applications can be built with txtai. The following runtimes are supported. + +- Container Orchestration (i.e. Kubernetes) +- Docker Engine +- Serverless + +Images for txtai are available on Docker Hub for [CPU](https://hub.docker.com/r/neuml/txtai-cpu) and [GPU](https://hub.docker.com/r/neuml/txtai-gpu) installs. The CPU install is recommended when GPUs aren't available given the image is half the size. + +The base txtai images have no models installed and models will be downloaded each time the container starts. Caching the models is recommended as that will significantly reduce container start times. This can be done a couple different ways. + +- Create a container with the [models cached](#cache-models-in-container-images) +- Set the transformers cache environment variable and mount that volume when starting the image + ```bash + docker run -v :/models -e TRANSFORMERS_CACHE=/models --rm --it + ``` + +## Build txtai images + +The txtai images found on Docker Hub are configured to support most situations. The Dockerfile is [available on GitHub](https://github.com/neuml/txtai/docker/base/Dockerfile). This image can be locally built with different options as desired. + +Examples build commands below + +```bash +# Get Dockerfile +wget https://raw.githubusercontent.com/neuml/txtai/master/docker/base/Dockerfile + +# Build Ubuntu 18.04 image running Python 3.7 +docker build -t txtai --build-arg BASE_IMAGE=ubuntu:18.04 --build-arg PYTHON_VERSION=3.7 . + +# Build image with GPU support +docker build -t txtai --build-arg GPU=1 . + +# Build minimal image with the base txtai components +docker build -t txtai --build-arg COMPONENTS= . +``` + +## Cache models in container images + +As mentioned previously, model caching is recommended to reduce container start times. The following commands demonstrate this. In all cases, it is assumed a config.yml file is present in the local directory with the desired configuration set. + +### API +```bash +# Get Dockerfile +wget https://raw.githubusercontent.com/neuml/txtai/master/docker/api/Dockerfile + +# CPU build +docker build -t txtai-api . + +# GPU build +docker build -t txtai-api --build-arg BASE_IMAGE=neuml/txtai-gpu . + +# Run +docker run -p 8000:8000 --rm -it txtai-api +``` + +### Service +```bash +# Get Dockerfile +wget https://raw.githubusercontent.com/neuml/txtai/master/docker/service/Dockerfile + +# CPU build +docker build -t txtai-service . + +# GPU build +docker build -t txtai-service --build-arg BASE_IMAGE=neuml/txtai-gpu . +``` + +### Workflow +```bash +# Get Dockerfile +wget https://raw.githubusercontent.com/neuml/txtai/master/docker/workflow/Dockerfile + +# CPU build +docker build -t txtai-workflow . + +# GPU build +docker build -t txtai-workflow --build-arg BASE_IMAGE=neuml/txtai-gpu . +``` diff --git a/docs/images/cloud-dark.png b/docs/images/cloud-dark.png new file mode 100644 index 00000000..15a4c0bd Binary files /dev/null and b/docs/images/cloud-dark.png differ diff --git a/docs/images/cloud.excalidraw b/docs/images/cloud.excalidraw new file mode 100644 index 00000000..93ae7abe --- /dev/null +++ b/docs/images/cloud.excalidraw @@ -0,0 +1,551 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 3020, + "versionNonce": 574956379, + "isDeleted": false, + "id": "Ufh5VUA3qmvJowuFyEWz4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 570.7515859513394, + "y": 59.768341009859626, + "strokeColor": "#d0d9dd", + "backgroundColor": "transparent", + "width": 719.330217768941, + "height": 100.49478848195537, + "seed": 1218477931, + "groupIds": [ + "xlXj5_q2htAY6GkFTIlfY" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "QPHaiLoteZgu8lrk22yKh" + } + ], + "updated": 1644866249723, + "link": null + }, + { + "type": "rectangle", + "version": 1748, + "versionNonce": 1788187605, + "isDeleted": false, + "id": "NDWbJgM53uVck-LSQzD-0", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 740.6664767795138, + "y": 67.41041395399299, + "strokeColor": "#ffeb3b", + "backgroundColor": "#ffeb3b", + "width": 85, + "height": 84.17917209201391, + "seed": 1934943141, + "groupIds": [ + "xlXj5_q2htAY6GkFTIlfY" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "xz4WiNbnK-neZmBN7EIi8" + }, + { + "type": "arrow", + "id": "OH6z46_TWktLzJb31zpRF" + }, + { + "type": "arrow", + "id": "b07pmUMsHbkm7ES3OaM12" + }, + { + "id": "HXM4G7z36lQFPmJTUAy8G", + "type": "arrow" + }, + { + "id": "BmtKHcjATX2AOVt484fin", + "type": "arrow" + }, + { + "id": "nR4v3IHv93pXRqc8UUXjz", + "type": "arrow" + }, + { + "id": "095ylTvsupuqCzyoolf44", + "type": "arrow" + }, + { + "type": "text", + "id": "0VztU7lb7T9I5zoL2LjLt" + } + ], + "updated": 1644866249723, + "link": null + }, + { + "id": "0VztU7lb7T9I5zoL2LjLt", + "type": "text", + "x": 745.6664767795138, + "y": 98.99999999999994, + "width": 75, + "height": 21, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "xlXj5_q2htAY6GkFTIlfY" + ], + "strokeSharpness": "sharp", + "seed": 604922325, + "version": 333, + "versionNonce": 1646133243, + "isDeleted": false, + "boundElements": null, + "updated": 1644866249723, + "link": null, + "text": "Container", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 15, + "containerId": "NDWbJgM53uVck-LSQzD-0", + "originalText": "Container" + }, + { + "type": "rectangle", + "version": 1824, + "versionNonce": 1455313205, + "isDeleted": false, + "id": "rr3YJsAyrrPqZbZ4qXh5x", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1175.874140249897, + "y": 70.42614920483032, + "strokeColor": "#00e676", + "backgroundColor": "#00e676", + "width": 85, + "height": 84.17917209201391, + "seed": 1158748789, + "groupIds": [ + "xlXj5_q2htAY6GkFTIlfY" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "xz4WiNbnK-neZmBN7EIi8" + }, + { + "type": "arrow", + "id": "OH6z46_TWktLzJb31zpRF" + }, + { + "type": "arrow", + "id": "b07pmUMsHbkm7ES3OaM12" + }, + { + "id": "HXM4G7z36lQFPmJTUAy8G", + "type": "arrow" + }, + { + "id": "BmtKHcjATX2AOVt484fin", + "type": "arrow" + }, + { + "id": "nR4v3IHv93pXRqc8UUXjz", + "type": "arrow" + }, + { + "id": "095ylTvsupuqCzyoolf44", + "type": "arrow" + }, + { + "id": "9NBdG6TyrBqjuDKvzAZXN", + "type": "text" + }, + { + "id": "9NBdG6TyrBqjuDKvzAZXN", + "type": "text" + }, + { + "type": "text", + "id": "9NBdG6TyrBqjuDKvzAZXN" + } + ], + "updated": 1644866249723, + "link": null + }, + { + "type": "text", + "version": 404, + "versionNonce": 938625179, + "isDeleted": false, + "id": "9NBdG6TyrBqjuDKvzAZXN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1180.874140249897, + "y": 102.01573525083728, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "width": 75, + "height": 21, + "seed": 603698011, + "groupIds": [ + "xlXj5_q2htAY6GkFTIlfY" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1644866249723, + "link": null, + "fontSize": 16, + "fontFamily": 1, + "text": "Container", + "baseline": 15, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "rr3YJsAyrrPqZbZ4qXh5x", + "originalText": "Container" + }, + { + "type": "rectangle", + "version": 1814, + "versionNonce": 766010005, + "isDeleted": false, + "id": "ji9UBE590YJ5e8DKUKbPG", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 598.874140249897, + "y": 68.42614920483032, + "strokeColor": "#ff7043", + "backgroundColor": "#ff7043", + "width": 85, + "height": 84.17917209201391, + "seed": 895199477, + "groupIds": [ + "xlXj5_q2htAY6GkFTIlfY" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "xz4WiNbnK-neZmBN7EIi8" + }, + { + "type": "arrow", + "id": "OH6z46_TWktLzJb31zpRF" + }, + { + "type": "arrow", + "id": "b07pmUMsHbkm7ES3OaM12" + }, + { + "id": "HXM4G7z36lQFPmJTUAy8G", + "type": "arrow" + }, + { + "id": "BmtKHcjATX2AOVt484fin", + "type": "arrow" + }, + { + "id": "nR4v3IHv93pXRqc8UUXjz", + "type": "arrow" + }, + { + "id": "095ylTvsupuqCzyoolf44", + "type": "arrow" + }, + { + "id": "2fdy7dRwF7ZVaYPrb1Q7p", + "type": "text" + }, + { + "id": "2fdy7dRwF7ZVaYPrb1Q7p", + "type": "text" + }, + { + "type": "text", + "id": "2fdy7dRwF7ZVaYPrb1Q7p" + } + ], + "updated": 1644866249723, + "link": null + }, + { + "type": "text", + "version": 394, + "versionNonce": 695500091, + "isDeleted": false, + "id": "2fdy7dRwF7ZVaYPrb1Q7p", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 603.874140249897, + "y": 100.01573525083728, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "width": 75, + "height": 21, + "seed": 507989211, + "groupIds": [ + "xlXj5_q2htAY6GkFTIlfY" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1644866249723, + "link": null, + "fontSize": 16, + "fontFamily": 1, + "text": "Container", + "baseline": 15, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ji9UBE590YJ5e8DKUKbPG", + "originalText": "Container" + }, + { + "type": "rectangle", + "version": 1754, + "versionNonce": 873023477, + "isDeleted": false, + "id": "Kod3erHdgkFtJcsgqHeQQ", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 889.874140249897, + "y": 68.42614920483032, + "strokeColor": "#03a9f4", + "backgroundColor": "#03a9f4", + "width": 85, + "height": 84.17917209201391, + "seed": 1941388379, + "groupIds": [ + "xlXj5_q2htAY6GkFTIlfY" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "xz4WiNbnK-neZmBN7EIi8" + }, + { + "type": "arrow", + "id": "OH6z46_TWktLzJb31zpRF" + }, + { + "type": "arrow", + "id": "b07pmUMsHbkm7ES3OaM12" + }, + { + "id": "HXM4G7z36lQFPmJTUAy8G", + "type": "arrow" + }, + { + "id": "BmtKHcjATX2AOVt484fin", + "type": "arrow" + }, + { + "id": "nR4v3IHv93pXRqc8UUXjz", + "type": "arrow" + }, + { + "id": "095ylTvsupuqCzyoolf44", + "type": "arrow" + }, + { + "id": "vA5QfvPySVZE9WnP6d7dp", + "type": "text" + }, + { + "type": "text", + "id": "vA5QfvPySVZE9WnP6d7dp" + } + ], + "updated": 1644866249723, + "link": null + }, + { + "type": "text", + "version": 337, + "versionNonce": 1644633563, + "isDeleted": false, + "id": "vA5QfvPySVZE9WnP6d7dp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 894.874140249897, + "y": 100.01573525083728, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "width": 75, + "height": 21, + "seed": 867011285, + "groupIds": [ + "xlXj5_q2htAY6GkFTIlfY" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1644866249723, + "link": null, + "fontSize": 16, + "fontFamily": 1, + "text": "Container", + "baseline": 15, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Kod3erHdgkFtJcsgqHeQQ", + "originalText": "Container" + }, + { + "type": "rectangle", + "version": 1863, + "versionNonce": 2078703957, + "isDeleted": false, + "id": "8p_AIsxC6UbaWESkrwl4g", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1035.874140249897, + "y": 69.42614920483032, + "strokeColor": "#5f3dc4", + "backgroundColor": "#7950f2", + "width": 85, + "height": 84.17917209201391, + "seed": 1712249051, + "groupIds": [ + "xlXj5_q2htAY6GkFTIlfY" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "xz4WiNbnK-neZmBN7EIi8" + }, + { + "type": "arrow", + "id": "OH6z46_TWktLzJb31zpRF" + }, + { + "type": "arrow", + "id": "b07pmUMsHbkm7ES3OaM12" + }, + { + "id": "HXM4G7z36lQFPmJTUAy8G", + "type": "arrow" + }, + { + "id": "BmtKHcjATX2AOVt484fin", + "type": "arrow" + }, + { + "id": "nR4v3IHv93pXRqc8UUXjz", + "type": "arrow" + }, + { + "id": "095ylTvsupuqCzyoolf44", + "type": "arrow" + }, + { + "id": "AceuLTPujneYjRp_njhIl", + "type": "text" + }, + { + "id": "AceuLTPujneYjRp_njhIl", + "type": "text" + }, + { + "id": "AceuLTPujneYjRp_njhIl", + "type": "text" + }, + { + "type": "text", + "id": "AceuLTPujneYjRp_njhIl" + } + ], + "updated": 1644866249723, + "link": null + }, + { + "type": "text", + "version": 443, + "versionNonce": 2092739195, + "isDeleted": false, + "id": "AceuLTPujneYjRp_njhIl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1040.874140249897, + "y": 101.01573525083728, + "strokeColor": "#000000", + "backgroundColor": "#7950f2", + "width": 75, + "height": 21, + "seed": 1770780245, + "groupIds": [ + "xlXj5_q2htAY6GkFTIlfY" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1644866249723, + "link": null, + "fontSize": 16, + "fontFamily": 1, + "text": "Container", + "baseline": 15, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "8p_AIsxC6UbaWESkrwl4g", + "originalText": "Container" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/docs/images/cloud.png b/docs/images/cloud.png new file mode 100644 index 00000000..4a9969c5 Binary files /dev/null and b/docs/images/cloud.png differ diff --git a/mkdocs.yml b/mkdocs.yml index 5e825957..9c42aaf3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -96,4 +96,5 @@ nav: - Configuration: api/configuration.md - Docker: api/docker.md - Methods: api/methods.md + - Cloud: cloud.md - Further Reading: further.md