Preparing and testing the serve script in R
In this recipe, we will create a serve
script using R that runs an inference API using the plumber
package. This API loads the model during initialization and uses the model to perform predictions during endpoint invocation.
The following diagram shows the expected behavior of the R serve
script that we will prepare in this recipe. The R serve
script loads the model file from the /opt/ml/model
directory and runs a plumber
web server on port 8080
:
The web server is expected to have the /ping
and /invocations
endpoints. This standalone R backend API server will run inside a custom container later.
Getting ready
Make sure you have completed the Preparing and testing the train script in R recipe.
How to do it...
We will start by preparing the api.r
file:
- Double-click the
api.r
file inside theml-r
directory in the file tree:Here, we can see four files under the
ml-r
directory. Remember that we created an emptyapi.r
file in the Setting up the Python and R experimentation environments recipe:In the next couple of steps, we will add a few lines of code inside this
api.r
file. Later, we will learn how to use the plumber package to generate an API from thisapi.r
file. - Define the
prepare_paths()
function, which we will use to initialize thePATHS
variable. This will help us manage the paths of the primary files and directories used in the script. This function allows us to initialize thePATHS
variable with a dictionary-like data structure, which we can use to get the absolute paths of the required files:prepare_paths <- function() { keys <- c('hyperparameters', 'input', 'data', 'model') values <- c('input/config/hyperparameters.json', 'input/config/inputdataconfig.json', 'input/data/', 'model/') paths <- as.list(values) names(paths) <- keys return(paths); } PATHS <- prepare_paths()
- Next, define the
get_path()
function, which makes use of thePATHS
variable from the previous step:get_path <- function(key) { output <- paste( '/opt/ml/', PATHS[[key]], sep="") return(output); }
- Create the following function (including the comments), which responds with
"OK"
when triggered from the/ping
endpoint:#* @get /ping function(res) { res$body <- "OK" return(res) }
The line containing
#* @get /ping
tellsplumber
that we will use this function to handle the GET requests with the/ping
route. - Define the
load_model()
function:load_model <- function() { model <- NULL filename <- paste0(get_path('model'), 'model') print(filename) model <- readRDS(filename) return(model) }
- Define the following
/invocations
function, which loads the model and uses it to perform a prediction on the input value from the request body:#* @post /invocations function(req, res) { print(req$postBody) model <- load_model() payload_value <- as.double(req$postBody) X_test <- data.frame(payload_value) colnames(X_test) <- "X" print(summary(model)) y_test <- predict(model, X_test) output <- y_test[[1]] print(output) res$body <- toString(output) return(res) }
Here, we loaded the model using the
load_model()
function, transformed and prepared the input payload before passing it to thepredict()
function, used thepredict()
function to perform the actual prediction when given anX
input value, and returned the predicted value in the request body.Tip
You can access a working copy of the
api.r
file in the Machine Learning with Amazon SageMaker Cookbook GitHub repository: https://github.com/PacktPublishing/Machine-Learning-with-Amazon-SageMaker-Cookbook/blob/master/Chapter02/ml-r/api.r.Now that the
api.r
file is ready, let's prepare theserve
script: - Double-click the
serve
file inside theml-r
directory in the file tree:It should open an empty
serve
file, similar to what is shown in the following screenshot:We will add the necessary code to this empty
serve
file in the next set of steps. - Start the
serve
script with the following lines of code. Here, we are loading theplumber
andhere
packages:#!/usr/bin/Rscript suppressWarnings(library(plumber)) library('here')
The
here
package provides utility functions to help us easily build paths to files (for example,api.r
). - Add the following lines of code to start the
plumber
API server:path <- paste0(here(), "/api.r") pr <- plumb(path) pr$run(host="0.0.0.0", port=8080)
Here, we used the
plumb()
andrun()
functions to launch the web server. It is important to note that the web server endpoint needs to run on port8080
for this to work correctly.Tip
You can access a working copy of the serve script in the Machine Learning with Amazon SageMaker Cookbook GitHub repository: https://github.com/PacktPublishing/Machine-Learning-with-Amazon-SageMaker-Cookbook/blob/master/Chapter02/ml-r/serve.
- Open a new Terminal tab:
Here, we see that a Terminal tab is already open. If you need to create a new one, simply click the plus (+) sign and then click New Terminal.
- Install
libcurl4-openssl-dev
andlibsodium-dev
usingapt-get install
. These are some of the prerequisites for installing theplumber
package:sudo apt-get install -y --no-install-recommends libcurl4-openssl-dev sudo apt-get install -y --no-install-recommends libsodium-dev
- Install the
here
package:sudo R -e "install.packages('here',repos='https://cloud.r-project.org')"
The
here
package helps us get the string path values we need to locate specific files (for example,api.r
). Feel free to check out https://cran.r-project.org/web/packages/here/index.html for more information. - Install the
plumber
package:sudo R -e "install.packages('plumber',repos='https://cloud.r-project.org')"
The plumber package allows us to generate an HTTP API in R. For more information, feel free to check out https://cran.r-project.org/web/packages/plumber/index.html.
- Navigate to the
ml-r
directory:cd /home/ubuntu/environment/opt/ml-r
- Make the
serve
script executable usingchmod
:chmod +x serve
- Run the
serve
script:./serve
This should yield log messages similar to the following ones:
Here, we can see that our serve script has successfully run a
plumber
API web server on port8080
.Finally, we must trigger this running web server.
- Open a new Terminal tab:
Here, we are creating a new Terminal tab as the first tab is already running the
serve
script. - Set the value of the
SERVE_IP
variable tolocalhost
:SERVE_IP=localhost
- Check if the
ping
endpoint is available withcurl
:curl http://$SERVE_IP:8080/ping
Running the previous line of code should yield an
OK
from the/ping
endpoint. - Test the
invocations
endpoint withcurl
:curl -d "1" -X POST http://$SERVE_IP:8080/invocations
We should get a value close to
881.342840085751
.
Now, let's see how this works!
How it works…
In this recipe, we prepared the serve
script in R. The serve
script makes use of the plumber
package to serve an API that allows GET requests for the /ping
route and POST requests for the /invocations
route. The serve
script is expected to load the model file(s) from the specified model directory and run a backend API server inside the custom container. This should provide a /ping
route and an /invocations
route.
Compared to its Python recipe counterpart, we are dealing with two files instead of one as that's how we used plumber in this recipe:
- The
api.r
file defines what the API looks like and how it behaves. - The
serve
script uses theapi.r
file to initialize and launch a web server using theplumb()
function from theplumber
package. Note that with Flask, there is no need to create a separate file to define the API routes.
When working with the plumber
package, we start with an R file describing how the API will behave (for example, api.r
). This R file follows the following format:
#* @get /ping function(res) { res$body <- "OK" return(<RETURN VALUE>) } #* @post /invocations function(req, res) { return(<RETURN VALUE>) }
Once this R file is ready, we simply create an R script that makes use of the plumb()
function from the plumber
package. This will launch a web server using the configuration and behavior coded in the api.r
file:
pr <- plumb(<PATH TO API.R>) pr$run(host="0.0.0.0", port=8080)
With this, whenever the /ping
URL is accessed, the mapped function defined in the api.r
file is executed. Similarly, whenever the /invocations
URL is accessed with a POST request, the corresponding mapped function is executed. For more information on the plumber
package, feel free to check out https://www.rplumber.io/.