Deploy

Review of previous steps

Show the code from previous steps
library(tidymodels)
library(vetiver)
library(pins)

car_mod <-
    workflow(mpg ~ ., decision_tree(mode = "regression")) %>%
    fit(mtcars)
v <- vetiver_model(car_mod, "cars_mpg")
model_board <- board_folder(".", versioned = TRUE)
model_board %>% vetiver_pin_write(v)
Show the code from previous steps
from vetiver.data import mtcars
from vetiver import VetiverModel, vetiver_pin_write
from sklearn import tree
from pins import board_folder

car_mod = tree.DecisionTreeRegressor().fit(mtcars, mtcars["mpg"])

v = VetiverModel(car_mod, model_name = "cars_mpg", 
                 save_ptype = True, ptype_data = mtcars)

model_board = board_folder(".", allow_pickle_read=True)
vetiver_pin_write(model_board, v)

Create a REST API for deployment

You can deploy your model by creating a special Plumber router in R or a FastAPI router in Python, and adding a POST endpoint for making predictions.

library(plumber)
pr() %>%
  vetiver_api(v)
# Plumber router with 2 endpoints, 4 filters, and 1 sub-router.
# Use `pr_run()` on this object to start the API.
├──[queryString]
├──[body]
├──[cookieParser]
├──[sharedSecret]
├──/logo
│  │ # Plumber static router serving from directory: /Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library/vetiver
├──/ping (GET)
└──/predict (POST)

To start a server using this object, pipe (%>%) to pr_run(port = 8080) or your port of choice.

from vetiver import VetiverAPI
app = VetiverAPI(v, check_ptype = True)

To start a server using this object, use app.run(port = 8080) or your port of choice.

You can interact with your vetiver API locally and debug it. FastAPI and Plumber APIs such as these can be hosted in a variety of ways. You can create a ready-to-go file for deployment that is especially suited for RStudio Connect.

vetiver_write_plumber(model_board, "cars_mpg")
# Generated by the vetiver package; edit with care

library(pins)
library(plumber)
library(rapidoc)
library(vetiver)

# Packages needed to generate model predictions
if (FALSE) {
    library(parsnip)
    library(rpart)
    library(workflows)
}
b <- board_folder(path = ".")
v <- vetiver_pin_read(b, "cars_mpg", version = "20220612T202117Z-a6f90")

#* @plumber
function(pr) {
    pr %>% vetiver_api(v)
}

For RStudio Connect, you can streamline this deployment process even more by using vetiver_deploy_rsconnect(model_board, "cars_mpg").

app_file = vetiver_write_app(model_board, "cars_mpg")
import vetiver
import pins


b = pins.board_folder('.')
v = vetiver.vetiver_pin_read(b, 'cars_mpg', version = '20220612T142120Z-d722a')

vetiver_api = vetiver.VetiverAPI(v)
api = vetiver_api.app

In a real-world situation, you would see something like board_rsconnect() or board_s3() here instead of our temporary demo board.

Important

Notice that the deployment is strongly linked to a specific version of the pinned model; if you pin another version of the model after you deploy your model, your deployed model will not be affected.

Generate a Dockerfile

For deploying a vetiver API to infrastructure other than RStudio Connect, such as Google Cloud Run, AWS, or Azure, you likely will want to build a Docker container.

Note

You can use any pins board with Docker, like board_folder() or board_rsconnect(), as long as your Docker container can authenticate to your pins board.

vetiver_write_docker(v)
# Generated by the vetiver package; edit with care

FROM rocker/r-ver:4.2.0
ENV RENV_CONFIG_REPOS_OVERRIDE https://packagemanager.rstudio.com/cran/latest

RUN apt-get update -qq && apt-get install -y --no-install-recommends \
  git \
  libcurl4-openssl-dev \
  libgit2-dev \
  libicu-dev \
  libsodium-dev \
  libssl-dev \
  make

COPY vetiver_renv.lock renv.lock
RUN Rscript -e "install.packages('renv')"
RUN Rscript -e "renv::restore()"
COPY plumber.R /opt/ml/plumber.R
EXPOSE 8000
ENTRYPOINT ["R", "-e", "pr <- plumber::plumb('/opt/ml/plumber.R'); pr$run(host = '0.0.0.0', port = 8000)"]

When you run vetiver_write_docker(), you generate two files: the Dockerfile itself and the renv.lock file to capture your model dependencies.

vetiver_write_docker(app_file)
# # Generated by the vetiver package; edit with care
# start with python base image
FROM python:3.8

# create directory in container for vetiver files
WORKDIR /vetiver

# copy  and install requirements
COPY vetiver_requirements.txt /vetiver/requirements.txt

#
RUN pip install --no-cache-dir --upgrade -r /vetiver/requirements.txt

# copy app file
COPY ./app.py /vetiver/app

# expose port
EXPOSE 8080

# run vetiver API
CMD ["uvicorn", "app.app:api", "--host", "0.0.0.0", "--port", "8080"]

To build the Docker image, you need two files: the Dockerfile itself generated via vetiver_write_docker() and a requirements.txt file to capture your model dependencies. If you don’t already have a requirements file for your project, vetiver.load_pkgs() will generate one for you, with the name vetiver_requirements.txt.

Tip
  • When you build such a Docker container with docker build, all the packages needed to make a prediction with your model are installed into the container.

  • When you run the Docker container, you can pass in environment variables (for authentication to your pins board, for example) with docker run --env-file .Renviron.

Predict from your model endpoint

A model deployed via vetiver can be treated as a special vetiver_endpoint() object.

endpoint <- vetiver_endpoint("http://127.0.0.1:8080/predict")
endpoint

── A model API endpoint for prediction: 
http://127.0.0.1:8080/predict
from vetiver.server import predict, vetiver_endpoint
endpoint = vetiver_endpoint("http://127.0.0.1:8080/predict/")
endpoint
'http://127.0.0.1:8080/predict/'

If such a deployed model endpoint is running via one process (either remotely on a server or locally, perhaps via a background job in the RStudio IDE), you can make predictions with that deployed model and new data in another, separate process1.

new_car <- tibble(cyl = 4,  disp = 200, 
                  hp = 100, drat = 3,
                  wt = 3,   qsec = 17, 
                  vs = 0,   am = 1,
                  gear = 4, carb = 2)
predict(endpoint, new_car)
# A tibble: 11 × 1
   .pred
   <chr>      
 1 22.3       
import pandas as pd
new_car_dict = {"cyl": [4], "disp": [200], 
                 "hp": [100], "drat": [3],
                 "wt": [3], "qsec": [17], 
                 "vs": [0], "am": [1],
                 "gear": [4], "carb": [2]}
new_car = pd.DataFrame(new_car_dict)
predict(endpoint, new_car)
  prediction
0       21.0

Being able to predict with a vetiver model endpoint takes advantage of the model’s input data prototype and other metadata that is stored with the model.

Footnotes

  1. Keep in mind that the R and Python models have different values for the decision tree hyperparameters.↩︎