SAS Model Manager enables users to publish open source models to container registries where they can be scored in real-time. Our enhancements to the R runtime container continues this trend while enhancing performance time and user experience. This article will walk through how to use the runtime container while also detailing improvements we've made.
1) Build, import, and publish your R model
The first step to use the R runtime container is to have an R model available in SAS Model Manager. R models can be generated via any IDE that supports R and packaged in a .rds or .rda file.
## Loading home equity data
hmeq <- read.csv("./data/hmeq_train.csv")
## Creating train/test/val
partition <- sample(c(1,2,3), replace = TRUE, prob = c(0.7, 0.2, 0.1), size = nrow(hmeq))
## Building Decision tree
library(rpart)
model <- rpart(formula = BAD ~ .,
data = hmeq[partition == 1,],
method = "class")
## Saving model
saveRDS(model, file.path('dtree.rds'), version = 2)
Code snippet for building and saving an R model
The associated registration files needed for the R model and optional files can be created using the open source package r-sasctl. The relevant functions provided by the package include write_in_out_json(), write_fileMetadata_json(), write_ModelProperties_json(), and diagnosticsJson(). The score code file can also be generated with r-sasctl using codegen(). However, users may need to edit this file to ensure that its matches what is specified in the User's Guide.
Once the model is packaged and the files are created, they can be zipped and imported into SAS Model Manager through the UI or r-sasctl's register_model() function. After registration, you can now publish the model. Supported destinations for R models in SAS Model Manager are listed in the User's Guide and include CAS, a Git repository, or a container publishing destination such as Amazon Web Services (AWS), Azure, Google Cloud Platform (GCP), or private docker. When the model is published, the base R runtime container is pulled down and built with the selected model. This process creates a unique image for every model and model version.
2) Score your R model
Now that we have a published R model, we can score with the R runtime container. Scoring can be done in one of two ways: using the Model Manager User Interface or using the docker image.
Scoring through the user interface
Publishing Validation in SAS Model Manager
When a model is first published, it becomes available on the publishing validation page for scoring. By clicking on the model we want to score and specifying the input table and other relevant information, we can then run publishing validation to initiate the scoring process.
If the run completes successfully, the results tab will show the output table along with the logs. Here's great time to highlight one of the advancements of the R runtime container: the performance time. The runtime container can now run using batch scoring by scoring the data within native R as opposed to scoring row-by-row with SAS' compute service. Performance time for row-by-row scoring increases (almost) linearly as the number of the rows in the dataset increase, leading to an increasingly inefficient process. With batch scoring, however, the score time is less dependent on the number of observations and, therefore, runs faster for larger data sets. We can see below that our dataset with 1k rows only took 0.063 seconds to score while our dataset with 100 times more rows took less than 10 times longer to score at 0.570 seconds.
INFO [2025-04-04 20:10:24] ["Score request received from POST /executions"]
INFO [2025-04-04 20:10:24] ["Score request content type: multipart/form-data; boundary=3b4e53f831f58e455c286cb0cad29371cc75ce24cacef40755894aef6e5c"]
INFO [2025-04-04 20:10:24] ["The data was successfully formatted into a data frame."]
WARN [2025-04-04 20:10:24] ["The data provided included additional data columns than were expected based off the model's input schema. The additional columns were BAD, LOAN, MORTDUE, VALUE \n"]
INFO [2025-04-04 20:10:24] ["The model was successfully scored in 0.0628557205200195 seconds."]
Scoring log with 1,000 row input dataset
INFO [2025-04-04 20:29:39] ["Score request received from POST /executions"]
INFO [2025-04-04 20:29:39] ["Score request content type: multipart/form-data; boundary=0cce5338852f5181e99cdae8fed781127daf2a0d3cb21b60138b4d9fe675"]
INFO [2025-04-04 20:29:41] ["The data was successfully formatted into a data frame."]
WARN [2025-04-04 20:29:41] ["The data provided included additional data columns than were expected based off the model's input schema. The additional columns were BAD, LOAN, MORTDUE, VALUE \n"]
INFO [2025-04-04 20:29:41] ["The model was successfully scored in 0.569653511047363 seconds."]
Scoring log with 100,000 row input dataset
Scoring with the docker image
We can also use the docker image created via publishing to score locally. After pulling the image, starting the container will prompt the Swagger document to appear. This document contains information on the available endpoints for the runtime container.
# pull image
docker pull <image>
# run image
docker run -itd -p <HostPort:ContainerPort> <sha-name>
Docker commands for pulling image and starting the container
Swagger documentation
We can then make calls against the container to score the model. Scoring can be done asynchronously, where the model is scored, but the results are not returned in the response, or synchronously, where results are returned. Relevant metadata is also returned in the json responses, including scoring time.
host = 'http://localhost:8083' # note: host port is specified in the docker run command
hmeq = pd.read_csv('hmeq_train.csv')
json_data = hmeq.to_json(orient='records')
## Asynchronous Scoring ##
response= requests.post(host + "/executions", json=json_data)
response.json()
# Response #
{'data': {},
'metadata': {'client_id': 'd0886f4a-8e53-444b-81c5-1f5ce5127606',
'elapsed_nanos': 147646903.9917,
'module_id': 'score.R',
'step_id': {},
'timestamp': '2025-03-12T16:27:55',
'transaction_id': 'f7b44fee-c406-4a84-99a3-477a04ce25ff',
'score_execution_time': 0.0231},
'version': 2,
'id': 'f7b44fee-c406-4a84-99a3-477a04ce25ff',
'codeFileUri': '',
'status': 201}
## Synchronous Scoring ##
response= requests.post(host + "/DemoRModel", json=json_data)
response.json()
# Response #
{'data': [{'EM_CLASSIFICATION': 0, 'EM_EVENTPROBABILITY': 0.0586},
...
'score_execution_time': 0.0076},
'version': 2,
'id': '2f921eac-adb5-4385-b7dd-9810268dbe11',
'codeFileUri': '',
'status': 200}
Examples of asynchronous and synchronous calls and results
3) Debug errors
Errors from the score code when scoring are bound to happen. Therefore, having a good framework for debugging is important. Luckily, another improvement to the R container runtime service is that the logs return detailed error messages when errors arise from running the score code. These are caught within the source code and returned to the log. Other tracked score code related errors are: when the score code is not valid or when the score function cannot be found.
scoreFunction <- function(REASON, JOB, YOJ, DEROG, DELINQ, CLAGE, NINQ, CLNO, DEBTINC) {
# Output: EM_CLASSIFICATION, EM_EVENTPROBABILITY
stop("Demo of error")
}
ERROR [2025-03-12 21:01:30] ["An exception occurred within the scoreFunction function of the score code: [...] [1] Demo of error attr(,class) [1] dump.frames\n"]
Example of R score code with error and the logs of the error.
Conclusion
The improved container scoring for R models allows users to score R models more efficiently and debug errors more effectively. What's more is that users don't have to update their current workflow to take advantage of these improvements.
... View more