Skip to content

Re-organizing examples #30

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

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
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
18 changes: 0 additions & 18 deletions .circleci/config.yml

This file was deleted.

51 changes: 51 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: CI

on:
push:
branches: ["master"]
pull_request:

jobs:
tests:
name: "Python ${{ matrix.python-version }}"
runs-on: "ubuntu-latest"
strategy:
fail-fast: false
matrix:
python-version: ["3.7"]

services:
redisai:
image: redislabs/redisai:edge-cpu-bionic
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout Code
uses: "actions/checkout@v2"
with:
lfs: true
- name: Setup Python
uses: "actions/setup-python@v2"
with:
python-version: ${{ matrix.python-version }}
- name: Cache dependencies
uses: actions/cache@v2
with:
path: /opt/venv
key: /opt/venv-${{ hashFiles('**/requirements.txt') }}
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: |
pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests
env:
REDIS_HOST: localhost
REDIS_PORT: 6379
run: |
pytest test.py
17 changes: 15 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,18 @@ ENV/
# js
node_modules

# pycharm
.idea
# editors
.idea
.vscode

# test output
.test_results

# model formats
*.pt
*.onnx
*.pb
*.pth
*.pbtxt
*.pkl
*.ckpt
294 changes: 294 additions & 0 deletions ImageClassificationWithPytorch.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "f0c2a6e5",
"metadata": {},
"source": [
"# Image Classification with PyTorch\n",
"Pytorch has been both researcher's and engineer's preferred choice of framework for DL development but when it comes to productionizing pytorch models, there still hasn't been a consensus on what to use. This guide run you through building a simple image classification model using Pytorch and then deploying that to RedisAI. Let's start with importing the necessary packages"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "1e657632",
"metadata": {},
"outputs": [],
"source": [
"import torchvision.models as models\n",
"import torch\n",
"\n",
"import json\n",
"import time\n",
"from redisai import Client\n",
"import ml2rt\n",
"from skimage import io\n",
"\n",
"import os\n",
"from redisai import Client"
]
},
{
"cell_type": "markdown",
"id": "43cd67a3",
"metadata": {},
"source": [
"## Build Model\n",
"For this example, we use a pretrained model from torchvision for image classification - the renowned resnet50. Since RedisAI is a C/C++ runtime, we'd need to export the torch model into [TorchScript](https://pytorch.org/docs/stable/jit.html). Here is how to do it but you can read more about TorchScript in the attached link"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "56edea97",
"metadata": {},
"outputs": [],
"source": [
"model = models.resnet50(pretrained=True)\n",
"model.eval()\n",
"\n",
"scripted_model = torch.jit.script(model)\n",
"torch.jit.save(scripted_model, 'resnet50.pt')"
]
},
{
"cell_type": "markdown",
"id": "75c8faa0",
"metadata": {},
"source": [
"## Setup RedisAI\n",
"This tutorial assumes you already have a RedisAI server running. The easiest way to setup one instance is using docker\n",
"\n",
"```\n",
"docker run -p 6379:6379 redislabs/redisai:latest-cpu-x64-bionic\n",
"```\n",
"\n",
"Take a look at this [quickstart](https://oss.redis.com/redisai/quickstart/) for more details. Here we setup the connection credentials and ping the server to verify we can talk "
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "59b6599a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"REDIS_HOST = os.getenv(\"REDIS_HOST\", \"localhost\")\n",
"REDIS_PORT = int(os.getenv(\"REDIS_PORT\", 6379))\n",
"con = Client(host=REDIS_HOST, port=REDIS_PORT)\n",
"con.ping()"
]
},
{
"cell_type": "markdown",
"id": "f68e8993",
"metadata": {},
"source": [
"## Load model\n",
"Next step is to load the model we trained above into RedisAI for serving. We are using a convinent package [ml2rt](https://pypi.org/project/ml2rt/) here for loading but it's not a mandatory dependency if you want to keep your `requirements.txt` small. Take a look at the `load_model` function. This will give us a binary blob of the model we have built above. We need to send this to RedisAI and also inform which backend we'd like to use and which device this should run on. We'll set the model on a key so we can reference this key later\n",
"\n",
"Note: If you want to run on GPU, take a look at the above quick start to setup RedisAI on GPU"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "f7ddde68",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'OK'"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = ml2rt.load_model(\"resnet50.pt\")\n",
"con.modelstore(\"pytorch_model\", backend=\"TORCH\", device=\"CPU\", data=model)"
]
},
{
"cell_type": "markdown",
"id": "c635e5f4",
"metadata": {},
"source": [
"## Load script\n",
"Why do you need Script? It's very likely that your deep learning model would have a pre/post processing step, like changing the dimensionality of the input (adding batch dimension) or doing normalizatoin etc. You normally do this from your client code and send the processed data to model server. With script, you can club this into your model serving pipeline. Script is one of the powerful feature of RedisAI. RedisAI Scripts are built on top of [TorchScript](https://pytorch.org/docs/stable/jit.html) and it's recommended to take a look if TorcScript is new to you. Torchscript is a subset of python programming langauge i.e it looks and smells like python but all the python functionalities are not available in torchscript. Now if you are wondering what's the benefit of TorchScript in RedisAI, there are few\n",
"\n",
"- It runs on a highly effecient C++ runtime\n",
"- It can pipeline your preprocessing and postprocessing jobs, right where your model and data resides. So no back and forth of huge data blobs between your model server and pre/post processing scripts\n",
"- It can run in a single redis pipeline or in RedisAI Dag which makes serving channel implementation smooth\n",
"- You can use it with any framework, not just pytorch\n",
"\n",
"You can load the script from a file (`ml2rt.load_script` does this for you) which is probably your workflow normally since you save the script in a file but here we pass the string into the `scriptstore` method"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "50bb90b1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'OK'"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"script = \"\"\"\n",
"def pre_process(tensors: List[Tensor], keys: List[str], args: List[str]):\n",
" image = tensors[0]\n",
" mean = torch.zeros(3).float().to(image.device)\n",
" std = torch.zeros(3).float().to(image.device)\n",
" mean[0], mean[1], mean[2] = 0.485, 0.456, 0.406\n",
" std[0], std[1], std[2] = 0.229, 0.224, 0.225\n",
" mean = mean.unsqueeze(1).unsqueeze(1)\n",
" std = std.unsqueeze(1).unsqueeze(1)\n",
" temp = image.float().div(255).permute(2, 0, 1)\n",
" return temp.sub(mean).div(std).unsqueeze(0)\n",
"\n",
"\n",
"def post_process(tensors: List[Tensor], keys: List[str], args: List[str]):\n",
" output = tensors[0]\n",
" return output.max(1)[1]\n",
"\"\"\"\n",
"con.scriptstore(\"processing_script\", device=\"CPU\", script=script, entry_points=(\"pre_process\", \"post_process\"))"
]
},
{
"cell_type": "markdown",
"id": "5b16d972",
"metadata": {},
"source": [
"## Load the image and final classes\n",
"Here we load the input image and the final classes to find the predicted output"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "fe95d716",
"metadata": {},
"outputs": [],
"source": [
"image = io.imread(\"data/cat.jpg\")\n",
"class_idx = json.load(open(\"data/imagenet_classes.json\"))"
]
},
{
"cell_type": "markdown",
"id": "9440afb8",
"metadata": {},
"source": [
"## Run the model serving pipeline\n",
"Here we run the serving pipeline one by one and finally fetch the results out. The pipeline is organized into 5 steps\n",
"\n",
"```\n",
"Setting Input -> Pre-processing Script -> Running Model -> Post-processing Script -> Fetching Output\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "f24ce05d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"281 tabby, tabby catamount\n"
]
}
],
"source": [
"con.tensorset('image', image)\n",
"con.scriptexecute('processing_script', 'pre_process', inputs='image', outputs='processed')\n",
"con.modelexecute('pytorch_model', 'processed', 'model_out')\n",
"con.scriptexecute('processing_script', 'post_process', inputs='model_out', outputs='final')\n",
"final = con.tensorget('final')\n",
"print(final[0], class_idx[str(final[0])])"
]
},
{
"cell_type": "markdown",
"id": "7b75d0ba",
"metadata": {},
"source": [
"## Running with DAG\n",
"Although this looks good, each of these calls has a network overhead of going back and forth and sometimes it's better to run everything as a single execution and that's what you can do with RedisAI DAG. DAGs are much more powerful than that but let's discuss that in another tutorial. Here we first setup a dag object and track all the operations we did above in the dag. Note that none of these tracking steps sends a request to RedisAI server. Once the dag object is ready with all the paths, you can trigger `dag.execute()` to initiate the DAG execution in RedisAI backend"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "40e02215",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"281 tabby, tabby catamount\n"
]
}
],
"source": [
"dag = con.dag(routing='default')\n",
"dag.tensorset('image', image)\n",
"dag.scriptexecute('processing_script', 'pre_process', inputs='image', outputs='processed')\n",
"dag.modelexecute('pytorch_model', 'processed', 'model_out')\n",
"dag.scriptexecute('processing_script', 'post_process', inputs='model_out', outputs='final')\n",
"dag.tensorget('final')\n",
"\n",
"final = dag.execute()[-1]\n",
"print(final[0], class_idx[str(final[0])])"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading