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("pins-r", 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.drop(columns="mpg"), mtcars["mpg"])

v = VetiverModel(car_mod, model_name = "cars_mpg", 
                 prototype_data = mtcars.drop(columns="mpg"))

model_board = board_folder("pins-py", 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 4 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: /home/runner/work/_temp/Library/vetiver
├──/metadata (GET)
├──/ping (GET)
├──/predict (POST)
└──/prototype (GET)

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_prototype=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 via automatically generated, detailed visual documentation.


FastAPI and Plumber APIs such as these can be hosted in a variety of ways. Let’s walk through two options: deploying to Posit Connect or with Docker.

Deploy to Connect

For Posit Connect, you can deploy your versioned model with a single function.

# authenticates via environment variables:
vetiver_deploy_rsconnect(model_board, "user.name/cars_mpg")
from rsconnect.api import RSConnectServer

connect_server = RSConnectServer(
    url=server_url, # load from an .env file
    api_key=api_key # load from an .env file 
)

vetiver.deploy_rsconnect(
    connect_server = connect_server,
    board = model_board,
    pin_name = "user.name/cars_mpg",
)

In this case, you probably want model_board to be a Connect pins board (board_connect()). For more on deploying to Connect, see the Connect documentation for using vetiver.

Prepare a Dockerfile

For deploying a vetiver API to infrastructure other than Posit 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_connect(), as long as your Docker container can authenticate to your pins board.

vetiver_prepare_docker(model_board, "cars_mpg")
The following required packages are not installed:
- cpp11     [required by lobstr, readr, tidyr, and 2 others]
- progress  [required by vroom]
Consider reinstalling these packages before snapshotting the lockfile.
# Generated by the vetiver package; edit with care

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

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

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_prepare_docker(), you generate three files needed to build a Docker image: the Dockerfile itself, a Plumber file serving your REST API, and the vetiver_renv.lock file to capture your model dependencies.

vetiver.prepare_docker(model_board, "cars_mpg")
# # Generated by the vetiver package; edit with care
# start with python base image
FROM python:3.10

# 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/app.py

# expose port
EXPOSE 8080

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

When you run vetiver.prepare_docker(), you generate three files needed to build a Docker image: the Dockerfile itself, a FastAPI app file serving your REST API, and a requirements.txt file to capture your model dependencies.

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.

  • Learn more about deploying with Docker.

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 Docker or 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.↩︎