diff --git a/.github/workflows/CD-docker_dev.yml b/.github/workflows/CD-docker_dev.yml new file mode 100644 index 0000000..6f638ab --- /dev/null +++ b/.github/workflows/CD-docker_dev.yml @@ -0,0 +1,42 @@ +name: CD | Dev Docker Image + +on: + push: + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Clear Space + run: | + rm -rf /usr/share/dotnet + rm -rf /opt/ghc + rm -rf "/usr/local/share/boost" + rm -rf "$AGENT_TOOLSDIRECTORY" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Determine Docker tag + id: docker-tag + run: | + if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then + echo "DOCKER_TAG=dev" >> $GITHUB_ENV + else + echo "DOCKER_TAG=${{ github.sha }}" >> $GITHUB_ENV + fi + + - name: Build and push + uses: docker/build-push-action@v4 + with: + push: true + tags: ${{ vars.DOCKERHUB_REPO }}/${{ vars.DOCKERHUB_IMG }}:${{ env.DOCKER_TAG }} diff --git a/.github/workflows/CD-docker_release.yml b/.github/workflows/CD-docker_release.yml new file mode 100644 index 0000000..b91cb24 --- /dev/null +++ b/.github/workflows/CD-docker_release.yml @@ -0,0 +1,34 @@ +name: CD | Release Docker Image + +on: + release: + types: [published] + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Clear Space + run: | + rm -rf /usr/share/dotnet + rm -rf /opt/ghc + rm -rf "/usr/local/share/boost" + rm -rf "$AGENT_TOOLSDIRECTORY" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v4 + with: + push: true + tags: ${{ vars.DOCKERHUB_REPO }}/${{ vars.DOCKERHUB_IMG }}:${{ github.event.release.tag_name }} diff --git a/.github/workflows/CI-runpod_dep.yml b/.github/workflows/CI-runpod_dep.yml new file mode 100644 index 0000000..5a14972 --- /dev/null +++ b/.github/workflows/CI-runpod_dep.yml @@ -0,0 +1,50 @@ +name: CI | Update runpod package version + +on: + repository_dispatch: + types: [python-package-release] + + push: + branches: ["main"] + + workflow_dispatch: + +jobs: + check_dep: + runs-on: ubuntu-latest + name: Check python requirements file and update + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Check for new package version and update + run: | + # Get current version + current_version=$(grep -oP 'runpod==\K[^"]+' ./builder/requirements.txt) + + # Get new version + new_version=$(curl -s https://pypi.org/pypi/runpod/json | jq -r .info.version) + echo "NEW_VERSION_ENV=$new_version" >> $GITHUB_ENV + + if [ -z "$new_version" ]; then + echo "Failed to fetch the new version." + exit 1 + fi + + # Check if the version is already up-to-date + if [ "$current_version" = "$new_version" ]; then + echo "The package version is already up-to-date." + exit 0 + fi + + # Update requirements.txt + sed -i "s/runpod==.*/runpod==$new_version/" ./builder/requirements.txt + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: Update package version + title: Update runpod package version + body: The package version has been updated to ${{ env.NEW_VERSION_ENV }} + branch: runpod-package-update diff --git a/.github/workflows/CI-test_worker.yml b/.github/workflows/CI-test_worker.yml new file mode 100644 index 0000000..f3cb0d9 --- /dev/null +++ b/.github/workflows/CI-test_worker.yml @@ -0,0 +1,70 @@ +name: CI | Test Worker + +on: + push: + branches: + - main + + pull_request: + branches: + - main + + workflow_dispatch: + +jobs: + initialize_worker: + runs-on: ubuntu-latest + outputs: + id: ${{ steps.extract_id.outputs.runpod_job_id }} + + steps: + - name: Deploy Worker + id: deploy + uses: fjogeleit/http-request-action@v1 + with: + url: "https://api.runpod.ai/v2/${{ secrets.RUNPOD_ENDPOINT }}/run" + method: "POST" + customHeaders: '{"Content-Type": "application/json"}' + bearerToken: ${{ secrets.RUNPOD_API_KEY }} + data: '{"input":{"github_pat": "${{ secrets.GH_PAT }}", "github_org":"${{ secrets.GH_ORG }}"}}' + + - name: Extract Job ID + id: extract_id + run: | + ID=$(echo '${{ steps.deploy.outputs.response }}' | jq -r '.id') + echo "::set-output name=runpod_job_id::$ID" + + run_tests: + needs: initialize_worker + runs-on: runpod + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python 3.11 & install dependencies + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r builder/requirements.txt + + - name: Execute Tests + run: | + python src/handler.py --test_input='{"input": "test"}' + + terminate_worker: + if: ${{ always() && !success() }} + needs: initialize_worker + runs-on: ubuntu-latest + + steps: + - name: Shutdown Worker + uses: fjogeleit/http-request-action@v1 + with: + url: "https://api.runpod.ai/v2/${{ secrets.RUNPOD_ENDPOINT }}/cancel/${{ needs.initialize_worker.outputs.id }}" + method: "POST" + customHeaders: '{"Content-Type": "application/json"}' + bearerToken: ${{ secrets.RUNPOD_API_KEY }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68bc17f --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f54af4f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +# Base image +FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu20.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# Use bash shell with pipefail option +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Set the working directory +WORKDIR / + +# Update and upgrade the system packages (Worker Template) +COPY builder/setup.sh /setup.sh +RUN /bin/bash /setup.sh && \ + rm /setup.sh + +# Install Python dependencies (Worker Template) +COPY builder/requirements.txt /requirements.txt +RUN python3 -m pip install --upgrade pip && \ + python3 -m pip install --upgrade -r /requirements.txt --no-cache-dir && \ + rm /requirements.txt + +# Add src files (Worker Template) +ADD src . + +CMD python3 -u /handler.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a80f426 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 runpod-workers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d870786 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +
+ +

Template | Worker

+ +[![CI | Test Worker](https://github.com/runpod-workers/worker-template/actions/workflows/CI-test_worker.yml/badge.svg)](https://github.com/runpod-workers/worker-template/actions/workflows/CI-test_worker.yml) +  +[![Docker Image](https://github.com/runpod-workers/worker-template/actions/workflows/CD-docker_dev.yml/badge.svg)](https://github.com/runpod-workers/worker-template/actions/workflows/CD-docker_dev.yml) + +🚀 | A simple worker that can be used as a starting point to build your own custom RunPod Endpoint API worker. +
+ +## 📖 | Getting Started + +1. Clone this repository. +2. (Optional) Add DockerHub credentials to GitHub Secrets. +3. Add your code to the `src` directory. +4. Update the `handler.py` file to load models and process requests. +5. Add any dependencies to the `requirements.txt` file. +6. Add any other build time scripts to the`builder` directory, for example, downloading models. +7. Update the `Dockerfile` to include any additional dependencies. + +### CI/CD + +This repository is setup to automatically build and push a docker image to the GitHub Container Registry. You will need to add the following to the GitHub Secrets for this repository to enable this functionality: + +- `DOCKERHUB_USERNAME` | Your DockerHub username for logging in. +- `DOCKERHUB_TOKEN` | Your DockerHub token for logging in. +- `DOCKERHUB_REPO` | The name of the repository you want to push to. +- `DOCKERHUB_IMG` | The name of the image you want to push to. + +The `CD-docker_dev.yml` file will build the image and push it to the `dev` tag, while the `CD-docker_release.yml` file will build the image on releases and tag it with the release version. + +The `CI-test_worker.yml` file will test the worker using the input provided by the `--test_input` argument when calling the file containing your handler. Be sure to update this workflow to install any dependencies you need to run your tests. + +## 💡 | Best Practices + +System dempendency installation, model caching, and other shell tasks should be added to the `builder/setup.sh` this will allow you to easily setup your Dockerfile as well as run CI/CD tasks. + +Models should be part of your docker image, this can be accomplished by either copying them into the image or downloading them during the build process. + +If using the input validation utility from the runpod python package, create a `schemas` python file where you can define the schemas, then import that file into your `handler.py` file. + +## 🔗 | Links + +🐳 [Docker Container](https://hub.docker.com/r/runpod/serverless-hello-world) diff --git a/builder/requirements.txt b/builder/requirements.txt new file mode 100644 index 0000000..4d8ab02 --- /dev/null +++ b/builder/requirements.txt @@ -0,0 +1,4 @@ +# Required Python packages get listed here, one per line. +# Reccomended to lock the version number to avoid unexpected changes. + +runpod==1.0.0 diff --git a/builder/setup.sh b/builder/setup.sh new file mode 100644 index 0000000..2b9926e --- /dev/null +++ b/builder/setup.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Stop script on error +set -e + +# Update System +apt-get update && apt-get upgrade -y + +# Install System Dependencies +# - openssh-server: for ssh access and web terminal +apt-get install -y --no-install-recommends software-properties-common curl git openssh-server + +# Install Python 3.10 +add-apt-repository ppa:deadsnakes/ppa -y +apt-get update && apt-get install -y --no-install-recommends python3.10 python3.10-dev python3.10-distutils +update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1 + +# Install pip for Python 3.10 +curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py +python3 get-pip.py + +# Clean up +apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* diff --git a/src/handler.py b/src/handler.py new file mode 100644 index 0000000..dc0c957 --- /dev/null +++ b/src/handler.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +''' Contains the handler function that will be called by the serverless. ''' + +import runpod + +# Load models into VRAM here so they can be warm between requests + + +def handler(event): + ''' + This is the handler function that will be called by the serverless. + ''' + print(event) + + # do the things + + # return the output that you want to be returned like pre-signed URLs to output artifacts + return "Hello World" + + +runpod.serverless.start({"handler": handler})