C API demo for inference. (#7151)
This commit is contained in:
parent
1369133916
commit
36346f8f56
@ -3,6 +3,7 @@ project(xgboost-c-examples)
|
|||||||
|
|
||||||
add_subdirectory(basic)
|
add_subdirectory(basic)
|
||||||
add_subdirectory(external-memory)
|
add_subdirectory(external-memory)
|
||||||
|
add_subdirectory(inference)
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
add_test(
|
add_test(
|
||||||
@ -15,3 +16,8 @@ add_test(
|
|||||||
COMMAND external-memory-demo
|
COMMAND external-memory-demo
|
||||||
WORKING_DIRECTORY ${xgboost-c-examples_BINARY_DIR}
|
WORKING_DIRECTORY ${xgboost-c-examples_BINARY_DIR}
|
||||||
)
|
)
|
||||||
|
add_test(
|
||||||
|
NAME test_xgboost_demo_c_inference
|
||||||
|
COMMAND inference-demo
|
||||||
|
WORKING_DIRECTORY ${xgboost-c-examples_BINARY_DIR}
|
||||||
|
)
|
||||||
|
|||||||
14
demo/c-api/inference/CMakeLists.txt
Normal file
14
demo/c-api/inference/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
project(inference-demo LANGUAGES C VERSION 0.0.1)
|
||||||
|
find_package(xgboost REQUIRED)
|
||||||
|
|
||||||
|
# xgboost is built as static libraries, all cxx dependencies need to be linked into the
|
||||||
|
# executable.
|
||||||
|
if (XGBOOST_BUILD_STATIC_LIB)
|
||||||
|
enable_language(CXX)
|
||||||
|
# find again for those cxx libraries.
|
||||||
|
find_package(xgboost REQUIRED)
|
||||||
|
endif(XGBOOST_BUILD_STATIC_LIB)
|
||||||
|
|
||||||
|
add_executable(inference-demo inference.c)
|
||||||
|
target_link_libraries(inference-demo PRIVATE xgboost::xgboost)
|
||||||
210
demo/c-api/inference/inference.c
Normal file
210
demo/c-api/inference/inference.c
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright 2021 XGBoost contributors
|
||||||
|
*
|
||||||
|
* \brief A simple example of using prediction functions.
|
||||||
|
*/
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <xgboost/c_api.h>
|
||||||
|
|
||||||
|
#define safe_xgboost(err) \
|
||||||
|
if ((err) != 0) { \
|
||||||
|
fprintf(stderr, "%s:%d: error in %s: %s\n", __FILE__, __LINE__, #err, \
|
||||||
|
XGBGetLastError()); \
|
||||||
|
exit(1); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define safe_malloc(ptr) \
|
||||||
|
if ((ptr) == NULL) { \
|
||||||
|
fprintf(stderr, "%s:%d: Failed to allocate memory.\n", __FILE__, \
|
||||||
|
__LINE__); \
|
||||||
|
exit(1); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define N_SAMPLES 128
|
||||||
|
#define N_FEATURES 16
|
||||||
|
|
||||||
|
typedef BoosterHandle Booster;
|
||||||
|
typedef DMatrixHandle DMatrix;
|
||||||
|
|
||||||
|
/* Row-major matrix */
|
||||||
|
struct _Matrix {
|
||||||
|
float *data;
|
||||||
|
size_t shape[2];
|
||||||
|
|
||||||
|
/* private members */
|
||||||
|
char _array_intrerface[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* A custom data type for demo. */
|
||||||
|
typedef struct _Matrix *Matrix;
|
||||||
|
|
||||||
|
/* Initialize matrix, copy data from `data` if it's not NULL. */
|
||||||
|
void Matrix_Create(Matrix *self, float const *data, size_t n_samples,
|
||||||
|
size_t n_features) {
|
||||||
|
if (self == NULL) {
|
||||||
|
fprintf(stderr, "Invalid pointer to %s\n", __func__);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
*self = (Matrix)malloc(sizeof(struct _Matrix));
|
||||||
|
safe_malloc(*self);
|
||||||
|
(*self)->data = (float *)malloc(n_samples * n_features * sizeof(float));
|
||||||
|
safe_malloc((*self)->data);
|
||||||
|
(*self)->shape[0] = n_samples;
|
||||||
|
(*self)->shape[1] = n_features;
|
||||||
|
|
||||||
|
if (data != NULL) {
|
||||||
|
memcpy((*self)->data, data,
|
||||||
|
(*self)->shape[0] * (*self)->shape[1] * sizeof(float));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate random matrix. */
|
||||||
|
void Matrix_Random(Matrix *self, size_t n_samples, size_t n_features) {
|
||||||
|
Matrix_Create(self, NULL, n_samples, n_features);
|
||||||
|
for (size_t i = 0; i < n_samples * n_features; ++i) {
|
||||||
|
float x = (float)rand() / (float)(RAND_MAX);
|
||||||
|
(*self)->data[i] = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Array interface specified by numpy. */
|
||||||
|
char const *Matrix_ArrayInterface(Matrix self) {
|
||||||
|
char const template[] = "{\"data\": [%lu, true], \"shape\": [%lu, %lu], "
|
||||||
|
"\"typestr\": \"<f4\", \"version\": 3}";
|
||||||
|
memset(self->_array_intrerface, '\0', sizeof(self->_array_intrerface));
|
||||||
|
sprintf(self->_array_intrerface, template, (size_t)self->data, self->shape[0],
|
||||||
|
self->shape[1]);
|
||||||
|
return self->_array_intrerface;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Matrix_NSamples(Matrix self) { return self->shape[0]; }
|
||||||
|
|
||||||
|
size_t Matrix_NFeatures(Matrix self) { return self->shape[1]; }
|
||||||
|
|
||||||
|
float Matrix_At(Matrix self, size_t i, size_t j) {
|
||||||
|
return self->data[i * self->shape[1] + j];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Matrix_Print(Matrix self) {
|
||||||
|
for (size_t i = 0; i < Matrix_NSamples(self); i++) {
|
||||||
|
for (size_t j = 0; j < Matrix_NFeatures(self); ++j) {
|
||||||
|
printf("%f, ", Matrix_At(self, i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Matrix_Free(Matrix self) {
|
||||||
|
if (self != NULL) {
|
||||||
|
if (self->data != NULL) {
|
||||||
|
self->shape[0] = 0;
|
||||||
|
self->shape[1] = 0;
|
||||||
|
free(self->data);
|
||||||
|
self->data = NULL;
|
||||||
|
}
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Matrix X;
|
||||||
|
Matrix y;
|
||||||
|
|
||||||
|
Matrix_Random(&X, N_SAMPLES, N_FEATURES);
|
||||||
|
Matrix_Random(&y, N_SAMPLES, 1);
|
||||||
|
|
||||||
|
char const *X_interface = Matrix_ArrayInterface(X);
|
||||||
|
char config[] = "{\"nthread\": 16, \"missing\": NaN}";
|
||||||
|
DMatrix Xy;
|
||||||
|
/* Dense means "dense matrix". */
|
||||||
|
safe_xgboost(XGDMatrixCreateFromDense(X_interface, config, &Xy));
|
||||||
|
/* Label must be in a contigious array. */
|
||||||
|
safe_xgboost(XGDMatrixSetDenseInfo(Xy, "label", y->data, y->shape[0], 1));
|
||||||
|
|
||||||
|
DMatrix cache[] = {Xy};
|
||||||
|
Booster booster;
|
||||||
|
/* Train a booster for demo. */
|
||||||
|
safe_xgboost(XGBoosterCreate(cache, 1, &booster));
|
||||||
|
|
||||||
|
size_t n_rounds = 10;
|
||||||
|
for (size_t i = 0; i < n_rounds; ++i) {
|
||||||
|
safe_xgboost(XGBoosterUpdateOneIter(booster, i, Xy));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save the trained model in JSON format. */
|
||||||
|
safe_xgboost(XGBoosterSaveModel(booster, "model.json"));
|
||||||
|
safe_xgboost(XGBoosterFree(booster));
|
||||||
|
|
||||||
|
/* Load it back for inference. The save and load is not required, only shown here for
|
||||||
|
* demonstration purpose. */
|
||||||
|
safe_xgboost(XGBoosterCreate(NULL, 0, &booster));
|
||||||
|
safe_xgboost(XGBoosterLoadModel(booster, "model.json"));
|
||||||
|
{
|
||||||
|
/* Run prediction with DMatrix object. */
|
||||||
|
char const config[] =
|
||||||
|
"{\"training\": false, \"type\": 0, "
|
||||||
|
"\"iteration_begin\": 0, \"iteration_end\": 0, \"strict_shape\": true}";
|
||||||
|
/* Shape of output prediction */
|
||||||
|
uint64_t const *out_shape;
|
||||||
|
/* Dimension of output prediction */
|
||||||
|
uint64_t out_dim;
|
||||||
|
/* Pointer to a thread local contigious array, assigned in prediction function. */
|
||||||
|
float const *out_results;
|
||||||
|
|
||||||
|
safe_xgboost(XGBoosterPredictFromDMatrix(booster, Xy, config, &out_shape,
|
||||||
|
&out_dim, &out_results));
|
||||||
|
if (out_dim != 2 || out_shape[0] != N_SAMPLES || out_shape[1] != 1) {
|
||||||
|
fprintf(stderr, "Regression model should output prediction as vector.");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix predt;
|
||||||
|
/* Always copy output from XGBoost before calling next API function. */
|
||||||
|
Matrix_Create(&predt, out_results, out_shape[0], out_shape[1]);
|
||||||
|
printf("Results from prediction\n");
|
||||||
|
Matrix_Print(predt);
|
||||||
|
Matrix_Free(predt);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
/* Run inplace prediction, which is faster and more memory efficient, but supports
|
||||||
|
* only basic inference types. */
|
||||||
|
char const config[] = "{\"type\": 0, \"iteration_begin\": 0, "
|
||||||
|
"\"iteration_end\": 0, \"strict_shape\": true, "
|
||||||
|
"\"cache_id\": 0, \"missing\": NaN}";
|
||||||
|
/* Shape of output prediction */
|
||||||
|
uint64_t const *out_shape;
|
||||||
|
/* Dimension of output prediction */
|
||||||
|
uint64_t out_dim;
|
||||||
|
/* Pointer to a thread local contigious array, assigned in prediction function. */
|
||||||
|
float const *out_results;
|
||||||
|
|
||||||
|
char const *X_interface = Matrix_ArrayInterface(X);
|
||||||
|
safe_xgboost(XGBoosterPredictFromDense(booster, X_interface, config, NULL,
|
||||||
|
&out_shape, &out_dim, &out_results));
|
||||||
|
|
||||||
|
if (out_dim != 2 || out_shape[0] != N_SAMPLES || out_shape[1] != 1) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Regression model should output prediction as vector, %lu, %lu",
|
||||||
|
out_dim, out_shape[0]);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix predt;
|
||||||
|
/* Always copy output from XGBoost before calling next API function. */
|
||||||
|
Matrix_Create(&predt, out_results, out_shape[0], out_shape[1]);
|
||||||
|
printf("Results from inplace prediction\n");
|
||||||
|
Matrix_Print(predt);
|
||||||
|
Matrix_Free(predt);
|
||||||
|
}
|
||||||
|
|
||||||
|
XGBoosterFree(booster);
|
||||||
|
|
||||||
|
XGDMatrixFree(Xy);
|
||||||
|
Matrix_Free(X);
|
||||||
|
Matrix_Free(y);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user