mirror of https://github.com/kubeflow/examples.git
Make it easier to demo serving and run in Katacoda (#107)
* Make it easier to demo serving and run in Katacoda * Allow the model path to be specified via environment variables so that we could potentially load the model from PVC. * Continue to bake the model into the image so that we don't need to train in order to serve. * Parameterize download_data.sh so we could potentially fetch different sources. * Update the Makefile so that we can build and set the image for the serving component. * Fix lint. * Update the serving docs.
This commit is contained in:
parent
26d68ead6c
commit
e12231bae3
|
@ -42,3 +42,6 @@ examples/.ipynb_checkpoints/
|
|||
# Data files
|
||||
*.h5
|
||||
*.dpkl
|
||||
|
||||
# Build directory
|
||||
github_issue_summarization/build/
|
|
@ -18,6 +18,12 @@ environments:
|
|||
server: https://35.188.73.10
|
||||
k8sVersion: v1.7.0
|
||||
path: jlewi
|
||||
kubecon-gh-demo-1:
|
||||
destination:
|
||||
namespace: kubeflow
|
||||
server: https://35.231.60.188
|
||||
k8sVersion: v1.7.0
|
||||
path: kubecon-gh-demo-1
|
||||
kind: ksonnet.io/app
|
||||
libraries:
|
||||
core:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Run a job to download the data to a persistent volume.
|
||||
//
|
||||
local env = std.extVar("__ksonnet/environments");
|
||||
local params = std.extVar("__ksonnet/params").components["data-pvc"];
|
||||
local overrideParams = std.extVar("__ksonnet/params").components["data-pvc"];
|
||||
local k = import "k.libsonnet";
|
||||
|
||||
|
||||
|
@ -20,6 +20,13 @@ local scriptConfigMap = {
|
|||
},
|
||||
};
|
||||
|
||||
local params = {
|
||||
// Default location for the data. Should be a directory on the PVC.
|
||||
"dataPath": "/data",
|
||||
"dataUrl": "https://storage.googleapis.com/kubeflow-examples/github-issue-summarization-data/github-issues.zip",
|
||||
"pvcName": "data-pvc",
|
||||
} + overrideParams;
|
||||
|
||||
local downLoader = {
|
||||
apiVersion: "batch/v1",
|
||||
kind: "Job",
|
||||
|
@ -36,6 +43,8 @@ local downLoader = {
|
|||
command: [
|
||||
"/bin/ash",
|
||||
"/scripts/download_data.sh",
|
||||
params.dataPath,
|
||||
params.dataUrl,
|
||||
],
|
||||
image: "busybox",
|
||||
name: "downloader",
|
||||
|
@ -62,7 +71,7 @@ local downLoader = {
|
|||
{
|
||||
name: "data",
|
||||
persistentVolumeClaim: {
|
||||
claimName: "data-pvc",
|
||||
claimName: params.pvcName,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Script to download the data
|
||||
# Usage
|
||||
# download_data.sh <URL of data> <data_dir>
|
||||
# e.g
|
||||
# download_data.sh https://storage.googleapis.com/kubeflow-examples/github-issue-summarization-data/github-issues.zip /data
|
||||
#
|
||||
# Script expects data to be a zip file
|
||||
set -ex
|
||||
|
||||
DATA_DIR=/data
|
||||
URL=$1
|
||||
DATA_DIR=$2
|
||||
|
||||
|
||||
mkdir -p ${DATA_DIR}
|
||||
|
||||
wget --directory-prefix=${DATA_DIR} \
|
||||
https://storage.googleapis.com/kubeflow-examples/github-issue-summarization-data/github-issues.zip
|
||||
|
||||
unzip -d ${DATA_DIR} ${DATA_DIR}/github-issues.zip
|
||||
wget --directory-prefix=${DATA_DIR} ${URL}
|
||||
TARGET=$(basename ${URL})
|
||||
unzip -d ${DATA_DIR} ${DATA_DIR}/${TARGET}
|
||||
|
|
|
@ -3,16 +3,96 @@ local params = std.extVar("__ksonnet/params").components["issue-summarization-mo
|
|||
local k = import "k.libsonnet";
|
||||
local serve = import "kubeflow/seldon/serve-simple.libsonnet";
|
||||
|
||||
// updatedParams uses the environment namespace if
|
||||
// the namespace parameter is not explicitly set
|
||||
local updatedParams = params {
|
||||
namespace: if params.namespace == "null" then env.namespace else params.namespace,
|
||||
};
|
||||
|
||||
local name = params.name;
|
||||
local image = params.image;
|
||||
local namespace = updatedParams.namespace;
|
||||
local namespace = env.namespace;
|
||||
local replicas = params.replicas;
|
||||
local endpoint = params.endpoint;
|
||||
|
||||
k.core.v1.list.new(serve.parts(namespace).serve(name, image, replicas, endpoint))
|
||||
local modelVar =
|
||||
if std.objectHas(params, "modelFile") then
|
||||
{
|
||||
name: "MODEL_FILE",
|
||||
value: params.modelFile,
|
||||
}
|
||||
else
|
||||
{};
|
||||
|
||||
local titleVar =
|
||||
if std.objectHas(params, "titleFile") then
|
||||
{
|
||||
name: "TITLE_PP_FILE",
|
||||
value: params.titleFile,
|
||||
}
|
||||
else
|
||||
{};
|
||||
|
||||
|
||||
local bodyVar =
|
||||
if std.objectHas(params, "bodyFile") then
|
||||
{
|
||||
name: "BODY_PP_FILE",
|
||||
value: params.bodyFile,
|
||||
}
|
||||
else
|
||||
{};
|
||||
|
||||
local containerEnv = [modelVar, titleVar, bodyVar];
|
||||
|
||||
# We don't use our ksonnet component because we want to override environment
|
||||
# variables and its just cleaner to copy paste the component.
|
||||
# TODO(https://github.com/kubeflow/kubeflow/issues/403) We should rewrite
|
||||
# our components to better support this.
|
||||
local serve = {
|
||||
apiVersion: "machinelearning.seldon.io/v1alpha1",
|
||||
kind: "SeldonDeployment",
|
||||
metadata: {
|
||||
labels: {
|
||||
app: "seldon",
|
||||
},
|
||||
name: params.name,
|
||||
namespace: env.namespace,
|
||||
},
|
||||
spec: {
|
||||
annotations: {
|
||||
deployment_version: "v1",
|
||||
project_name: params.name,
|
||||
},
|
||||
name: name,
|
||||
predictors: [
|
||||
{
|
||||
annotations: {
|
||||
predictor_version: "v1",
|
||||
},
|
||||
componentSpec: {
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
image: params.image,
|
||||
imagePullPolicy: "Always",
|
||||
name: params.name,
|
||||
env: std.prune(containerEnv),
|
||||
},
|
||||
],
|
||||
terminationGracePeriodSeconds: 1,
|
||||
},
|
||||
},
|
||||
graph: {
|
||||
children: [
|
||||
|
||||
],
|
||||
endpoint: {
|
||||
type: endpoint,
|
||||
},
|
||||
name: name,
|
||||
type: "MODEL",
|
||||
},
|
||||
name: name,
|
||||
replicas: replicas,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
k.core.v1.list.new(serve)
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
"issue-summarization-model-serving": {
|
||||
endpoint: "REST",
|
||||
image: "null",
|
||||
image: "gcr.io/kubeflow-examples/issue-summarization-model:v20180427-e2aa113",
|
||||
name: "issue-summarization",
|
||||
namespace: "null",
|
||||
replicas: 2,
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
local base = import "base.libsonnet";
|
||||
local k = import "k.libsonnet";
|
||||
|
||||
base + {
|
||||
// Insert user-specified overrides here. For example if a component is named "nginx-deployment", you might have something like:
|
||||
// "nginx-deployment"+: k.deployment.mixin.metadata.labels({foo: "bar"})
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
local params = import "../../components/params.libsonnet";
|
||||
params + {
|
||||
components +: {
|
||||
// Insert component parameter overrides here. Ex:
|
||||
// guestbook +: {
|
||||
// name: "guestbook-dev",
|
||||
// replicas: params.global.replicas,
|
||||
// },
|
||||
},
|
||||
}
|
|
@ -5,6 +5,8 @@ Uses trained model files to generate a prediction.
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import dill as dpickle
|
||||
from keras.models import load_model
|
||||
|
@ -13,13 +15,21 @@ from seq2seq_utils import Seq2Seq_Inference
|
|||
class IssueSummarization(object):
|
||||
|
||||
def __init__(self):
|
||||
with open('body_pp.dpkl', 'rb') as body_file:
|
||||
body_pp_file = os.getenv('BODY_PP_FILE', 'body_pp.dpkl')
|
||||
print('body_pp file {0}'.format(body_pp_file))
|
||||
with open(body_pp_file, 'rb') as body_file:
|
||||
body_pp = dpickle.load(body_file)
|
||||
with open('title_pp.dpkl', 'rb') as title_file:
|
||||
|
||||
title_pp_file = os.getenv('TITLE_PP_FILE', 'title_pp.dpkl')
|
||||
print('title_pp file {0}'.format(title_pp_file))
|
||||
with open(title_pp_file, 'rb') as title_file:
|
||||
title_pp = dpickle.load(title_file)
|
||||
|
||||
model_file = os.getenv('MODEL_FILE', 'seq2seq_model_tutorial.h5')
|
||||
print('model file {0}'.format(model_file))
|
||||
self.model = Seq2Seq_Inference(encoder_preprocessor=body_pp,
|
||||
decoder_preprocessor=title_pp,
|
||||
seq2seq_model=load_model('seq2seq_model_tutorial.h5'))
|
||||
seq2seq_model=load_model(model_file))
|
||||
|
||||
def predict(self, input_text, feature_names): # pylint: disable=unused-argument
|
||||
return np.asarray([[self.model.generate_issue_title(body[0])[1]] for body in input_text])
|
||||
|
|
|
@ -31,17 +31,28 @@ else
|
|||
TAG := $(shell date +v%Y%m%d)-$(shell git describe --tags --always --dirty)-$(shell git diff | shasum -a256 | cut -c -6)
|
||||
endif
|
||||
|
||||
DIR := ${CURDIR}
|
||||
DIR := $(shell pwd)
|
||||
|
||||
# Use a subdirectory of the root directory
|
||||
# this way it will be excluded by git diff-files
|
||||
BUILD_DIR := $(shell cd ../build/notebook_build && pwd)
|
||||
|
||||
MODEL_GCS := gs://kubeflow-examples-data/gh_issue_summarization/model/v20180426
|
||||
# You can override this on the command line as
|
||||
# make PROJECT=kubeflow-examples <target>
|
||||
PROJECT := kubeflow-examples
|
||||
|
||||
IMG := gcr.io/$(PROJECT)/tf-job-issue-summarization
|
||||
|
||||
# gcr.io is prepended automatically by Seldon's builder.
|
||||
MODEL_IMG_NAME := $(PROJECT)/issue-summarization-model
|
||||
MODEL_IMG := gcr.io/$(MODEL_IMG_NAME)
|
||||
|
||||
echo:
|
||||
@echo changed files $(CHANGED_FILES)
|
||||
@echo tag $(TAG)
|
||||
@echo BUILD_DIR=$(BUILD_DIR)
|
||||
|
||||
push: build
|
||||
gcloud docker -- push $(IMG):$(TAG)
|
||||
|
||||
|
@ -53,4 +64,44 @@ set-image: push
|
|||
# export DOCKER_BUILD_OPTS=--no-cache
|
||||
build:
|
||||
docker build ${DOCKER_BUILD_OPTS} -f Dockerfile -t $(IMG):$(TAG) ./
|
||||
@echo Built $(IMG):$(TAG)
|
||||
@echo Built $(IMG):$(TAG)
|
||||
|
||||
$(BUILD_DIR)/body_pp.dpkl:
|
||||
mkdir -p model
|
||||
gsutil cp $(MODEL_GCS)/body_pp.dpkl $(BUILD_DIR)/
|
||||
|
||||
$(BUILD_DIR)/title_pp.dpkl:
|
||||
mkdir -p model
|
||||
gsutil cp $(MODEL_GCS)/title_pp.dpkl $(BUILD_DIR)/
|
||||
|
||||
$(BUILD_DIR)/seq2seq_model_tutorial.h5:
|
||||
mkdir -p model
|
||||
gsutil cp $(MODEL_GCS)/seq2seq_model_tutorial.h5 $(BUILD_DIR)/
|
||||
|
||||
# Copy python files into the model directory
|
||||
# so that we can mount a single directory into the container
|
||||
$(BUILD_DIR)/% : %
|
||||
cp -f $< $@
|
||||
|
||||
download-model: $(BUILD_DIR)/seq2seq_model_tutorial.h5 $(BUILD_DIR)/title_pp.dpkl $(BUILD_DIR)/body_pp.dpkl
|
||||
|
||||
# TODO(jlewi): This doesn't actually pick up changes to code. The problem is that docker run is not detecting when code changes
|
||||
# and when it does copying it to $(BUILD_DIR)/build. Using --force fixes that problem but then it looks like docker build
|
||||
# no longer uses the cache and rebuilds are slow. Manually copying the files doesn't work because the files end up
|
||||
# owned by root because they were created inside the container.
|
||||
# The work around now is to manually copy files (e.g. in a shell outside Make) as needed
|
||||
build-model-image: download-model $(BUILD_DIR)/seq2seq_utils.py $(BUILD_DIR)/IssueSummarization.py $(BUILD_DIR)/requirements.txt
|
||||
# The docker run comand creates a Dockerfile for the seldon image with required assets
|
||||
docker run -v $(BUILD_DIR):/my_model seldonio/core-python-wrapper:0.7 /my_model IssueSummarization $(TAG) gcr.io --base-image=python:3.6 --image-name=$(MODEL_IMG_NAME)
|
||||
# We don't use the script generated by Seldon because that script won't get updated by make to reflect the change
|
||||
# in the desired tag.
|
||||
docker build -t $(MODEL_IMG):$(TAG) -f $(BUILD_DIR)/build/Dockerfile $(BUILD_DIR)/build
|
||||
@echo built $(MODEL_IMG):$(TAG)
|
||||
|
||||
push-model-image: build-model-image
|
||||
echo pushing $(MODEL_IMG):$(TAG)
|
||||
gcloud docker -- push $(MODEL_IMG):$(TAG)
|
||||
|
||||
set-model-image: push-model-image
|
||||
# Set the image to use
|
||||
cd ../ks-kubeflow && ks param set issue-summarization-model-serving image $(MODEL_IMG):$(TAG)
|
||||
|
|
|
@ -4,15 +4,20 @@ We are going to use [seldon-core](https://github.com/SeldonIO/seldon-core) to se
|
|||
|
||||
> The model is written in Keras and when exported as a TensorFlow model seems to be incompatible with TensorFlow Serving. So we're using seldon-core to serve this model since seldon-core allows you to serve any arbitrary model. More details [here](https://github.com/kubeflow/examples/issues/11#issuecomment-371005885).
|
||||
|
||||
# Prerequisites
|
||||
# Building a model server
|
||||
|
||||
Ensure that you have the following files from the [training](training_the_model.md) step in your `notebooks` directory:
|
||||
You have two options for getting a model server
|
||||
|
||||
* `seq2seq_model_tutorial.h5` - the keras model
|
||||
* `body_pp.dpkl` - the serialized body preprocessor
|
||||
* `title_pp.dpkl` - the serialized title preprocessor
|
||||
1. You can use the public model server image `gcr.io/kubeflow-examples/issue-summarization-model`
|
||||
|
||||
# Wrap the model into a seldon-core microservice
|
||||
* This server has a copy of the model and supporting assets baked into the container image
|
||||
* So you can just run this image to get a pre-trained model
|
||||
* Serving your own model using this server is discussed below
|
||||
|
||||
1. You can build your own model server as discussed below
|
||||
|
||||
|
||||
## Wrap the model into a seldon-core microservice
|
||||
|
||||
cd into the notebooks directory and run the following docker command. This will create a build/ directory.
|
||||
|
||||
|
@ -36,6 +41,16 @@ You can push the image by running `gcloud docker -- push gcr.io/gcr-repository-n
|
|||
|
||||
> You can find more details about wrapping a model with seldon-core [here](https://github.com/SeldonIO/seldon-core/blob/master/docs/wrappers/python.md)
|
||||
|
||||
### Storing a model in the Docker image
|
||||
|
||||
If you want to store a copy of the model in the Docker image make sure the following files are available in the directory in which you run
|
||||
the commands in the previous steps. These files are produced by the [training](training_the_model.md) step in your `notebooks` directory:
|
||||
|
||||
* `seq2seq_model_tutorial.h5` - the keras model
|
||||
* `body_pp.dpkl` - the serialized body preprocessor
|
||||
* `title_pp.dpkl` - the serialized title preprocessor
|
||||
|
||||
|
||||
# Deploying the model to your kubernetes cluster
|
||||
|
||||
Now that we have an image with our model server, we can deploy it to our kubernetes cluster. We need to first deploy seldon-core to our cluster.
|
||||
|
|
Loading…
Reference in New Issue