Skip to content

Commit

Permalink
[C API] Continue implementation (#409)
Browse files Browse the repository at this point in the history
### Summary:

Continue with basic implementation of a C API for Neuropod. Issue #407 

### Test Plan:

Added a test that loads a model and fails, runs inference successfully and fails, and verifies the name and platform.
  • Loading branch information
vkuzmin-uber authored Aug 11, 2020
1 parent 20b6e1d commit c16ad53
Show file tree
Hide file tree
Showing 8 changed files with 420 additions and 117 deletions.
13 changes: 13 additions & 0 deletions source/neuropod/bindings/c/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# C API

***NOTE: This API is currently experimental***

## Build
```
bazel build //neuropod/bindings/c:c_api
```

## Run C unit tests
```
bazel test --test_output=errors //neuropod/tests:test_c_api
```
107 changes: 97 additions & 10 deletions source/neuropod/bindings/c/c_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,119 @@ limitations under the License.
#include "neuropod/bindings/c/c_api.h"

#include "neuropod/bindings/c/c_api_internal.h"
#include "neuropod/bindings/c/np_status_internal.h"
#include "neuropod/bindings/c/np_tensor_allocator_internal.h"
#include "neuropod/bindings/c/np_valuemap_internal.h"

// Load a model given a path
void NP_LoadNeuropod(const char *neuropod_path, NP_Neuropod **model, NP_Status *status)
#include <exception>
#include <string>
#include <vector>

void NP_LoadNeuropodWithOpts(const char * neuropod_path,
const NP_RuntimeOptions *options,
NP_Neuropod ** model,
NP_Status * status)
{
*model = new NP_Neuropod();
try
{
*model = new NP_Neuropod();
(*model)->model = std::make_unique<neuropod::Neuropod>(neuropod_path);
NP_ClearStatus(status);
}
catch (std::exception &e)
{
status->code = NEUROPOD_ERROR;
status->message = e.what();
}
}

NP_RuntimeOptions NP_DefaultRuntimeOptions()
{
// Use C++ runtime options object to set default values.
neuropod::RuntimeOptions default_options;
NP_RuntimeOptions options;

options.use_ope = default_options.use_ope;
options.visible_device = static_cast<NP_RuntimeOptions::NP_Device>(default_options.visible_device);
options.load_model_at_construction = default_options.load_model_at_construction;
options.disable_shape_and_type_checking = default_options.disable_shape_and_type_checking;

auto ope_options = &options.ope_options;
ope_options->free_memory_every_cycle = default_options.ope_options.free_memory_every_cycle;
// Control queue name is empty, a new worker will be started.
// This is what is expected by default.
ope_options->control_queue_name[0] = '\0';

(*model)->model = std::make_unique<neuropod::Neuropod>(neuropod_path);
return options;
}

void NP_LoadNeuropod(const char *neuropod_path, NP_Neuropod **model, NP_Status *status)
{
const auto &options = NP_DefaultRuntimeOptions();
NP_LoadNeuropodWithOpts(neuropod_path, &options, model, status);
}

// Free a model
void NP_FreeNeuropod(NP_Neuropod *model)
{
delete model;
}

// Run inference
// Note: The caller is responsible for freeing the returned NP_NeuropodValueMap
void NP_Infer(NP_Neuropod *model, const NP_NeuropodValueMap *inputs, NP_NeuropodValueMap **outputs, NP_Status *status)
{
*outputs = new NP_NeuropodValueMap();
(*outputs)->data = std::move(*model->model->infer(inputs->data));
NP_InferWithRequestedOutputs(model, inputs, 0, nullptr, outputs, status);
}

void NP_InferWithRequestedOutputs(NP_Neuropod * model,
const NP_NeuropodValueMap *inputs,
size_t noutputs,
const char ** requested_outputs,
NP_NeuropodValueMap ** outputs,
NP_Status * status)
{
try
{
// By default empty collection of requested_ouputs expected.
// Note that it copies C-strings (0-terminated) from given array.
// User must guarantee that noutputs and requested_outputs address valid data..
std::vector<std::string> rout;
if (requested_outputs != nullptr)
{
for (size_t i = 0; i < noutputs; ++i)
{
rout.push_back(requested_outputs[i]);
}
}
*outputs = new NP_NeuropodValueMap();
(*outputs)->data = std::move(*model->model->infer(inputs->data, rout));
NP_ClearStatus(status);
}
catch (std::exception &e)
{
status->code = NEUROPOD_ERROR;
status->message = e.what();
}
}

const char *NP_GetName(NP_Neuropod *model)
{
return model->model->get_name().c_str();
}

const char *NP_GetPlatform(NP_Neuropod *model)
{
return model->model->get_platform().c_str();
}

size_t NP_GetNumInputs(NP_Neuropod *model)
{
return model->model->get_inputs().size();
}

size_t NP_GetNumOutputs(NP_Neuropod *model)
{
return model->model->get_outputs().size();
}

// Get an allocator for a model
NP_TensorAllocator *NP_GetAllocator(NP_Neuropod *model)
{
auto out = new NP_TensorAllocator();
Expand Down
67 changes: 51 additions & 16 deletions source/neuropod/bindings/c/c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,54 @@ limitations under the License.
#include "neuropod/bindings/c/np_tensor_spec.h"
#include "neuropod/bindings/c/np_valuemap.h"

#include <stdbool.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct NP_Neuropod NP_Neuropod;

// Load a model given a path
// Load a model given a path.
void NP_LoadNeuropod(const char *neuropod_path, NP_Neuropod **model, NP_Status *status);

// TODO: Add more options
// Runtime Options. See description of options at neuropod/options.hh
#define QUEUENAME_MAX 256
typedef struct NP_RuntimeOptions
{
// Whether or not to use out-of-process execution
// (using shared memory to communicate between the processes)
// Whether or not to use out-of-process execution.
bool use_ope;

// These options are only used if use_ope is set to true.
struct NP_OPEOptions
{
bool free_memory_every_cycle;
char control_queue_name[QUEUENAME_MAX];
} ope_options;

// C enum that matches neuropod::NeuropodDevice
enum NP_Device
{
CPU = -1,
GPU0 = 0,
GPU1 = 1,
GPU2 = 2,
GPU3 = 3,
GPU4 = 4,
GPU5 = 5,
GPU6 = 6,
GPU7 = 7
} visible_device;

bool load_model_at_construction;
bool disable_shape_and_type_checking;
} NP_RuntimeOptions;

// Load a model given a path and options
// Creates default runtime options that used implicitly when load model w/o options.
// This can be useful if need to use default option except some flags.
NP_RuntimeOptions NP_DefaultRuntimeOptions();

// Load a model given a path and options.
void NP_LoadNeuropodWithOpts(const char * neuropod_path,
const NP_RuntimeOptions *options,
NP_Neuropod ** model,
Expand All @@ -50,21 +79,27 @@ void NP_LoadNeuropodWithOpts(const char * neuropod_path,
// Free a model
void NP_FreeNeuropod(NP_Neuropod *model);

// Run inference
// Note: The caller is responsible for freeing the returned NP_NeuropodValueMap
// Run inference.
// Note: The caller is responsible for freeing the returned NP_NeuropodValueMap.
// Number of elements in outputs is equal to NP_GetNumOutputs.
void NP_Infer(NP_Neuropod *model, const NP_NeuropodValueMap *inputs, NP_NeuropodValueMap **outputs, NP_Status *status);

// Run inference with a set of requested outputs
// requested_outputs should be a null terminated array of char ptr containing the names of requested outputs
// Note: The caller is responsible for freeing the returned NP_NeuropodValueMap
// Run inference with a set of requested outputs.
// requested_outputs should be array of char ptr containing the names of requested outputs.
// noutputs is a sort of in/out param that specifies number of elements in requested_outputs and
// number of elements in outputs after execution.
// Note: The caller is responsible for freeing the returned NP_NeuropodValueMap.
void NP_InferWithRequestedOutputs(NP_Neuropod * model,
const NP_NeuropodValueMap *inputs,
size_t noutput,
const char ** requested_outputs,
NP_NeuropodValueMap ** outputs,
NP_Status * status);

// Get information about the model
// Get name of the model.
const char *NP_GetName(NP_Neuropod *model);

// Get platform identfier of the model.
const char *NP_GetPlatform(NP_Neuropod *model);

// Get the number of items in the input spec of a model
Expand All @@ -73,17 +108,17 @@ size_t NP_GetNumInputs(NP_Neuropod *model);
// Get the number of items in the output spec of a model
size_t NP_GetNumOutputs(NP_Neuropod *model);

// Get an item from the input spec of a model
// Returns nullptr if index is out of range
// Get an item from the input spec of a model.
// Returns nullptr if index is out of range.
// Note: The caller is responsible for freeing the returned TensorSpec
NP_TensorSpec *NP_GetInputSpec(NP_Neuropod *model, size_t index);

// Get an item from the output spec of a model
// Returns nullptr if index is out of range
// Get an item from the output spec of a model.
// Returns nullptr if index is out of range.
// Note: The caller is responsible for freeing the returned TensorSpec
NP_TensorSpec *NP_GetOutputSpec(NP_Neuropod *model, size_t index);

// Get an allocator for a model
// Get an allocator for a model.
// Note: The caller is responsible for freeing the returned TensorAllocator
NP_TensorAllocator *NP_GetAllocator(NP_Neuropod *model);

Expand Down
16 changes: 10 additions & 6 deletions source/neuropod/bindings/c/np_tensor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@ limitations under the License.
#include "neuropod/bindings/c/np_tensor_internal.h"
#include "neuropod/internal/neuropod_tensor_raw_data_access.hh"

// For non-string tensors, get a pointer to the underlying data
// Returns nullptr if called on a string tensor
void *NP_GetData(NP_NeuropodTensor *tensor)
{
return neuropod::internal::NeuropodTensorRawDataAccess::get_untyped_data_ptr(*tensor->tensor->as_tensor());
}

// Releases a tensor. The memory might not be deallocated immediately if the tensor is still
// referenced by a value-map object or used by an infer operation.
// This should be called on every tensor returned by the C API.
// See the notes in `np_valuemap.h` and `np_tensor_allocator.h` for more detail.
const void *NP_GetDataReadOnly(const NP_NeuropodTensor *tensor)
{
return neuropod::internal::NeuropodTensorRawDataAccess::get_untyped_data_ptr(*tensor->tensor->as_tensor());
}

size_t NP_GetNumElements(const NP_NeuropodTensor *tensor)
{
return tensor->tensor->as_tensor()->get_num_elements();
}

void NP_FreeTensor(NP_NeuropodTensor *tensor)
{
delete tensor;
Expand Down
3 changes: 3 additions & 0 deletions source/neuropod/bindings/c/np_tensor.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ void *NP_GetData(NP_NeuropodTensor *tensor);
// Returns nullptr if called on a string tensor
const void *NP_GetDataReadOnly(const NP_NeuropodTensor *tensor);

// Returns number of elements in tensor.
size_t NP_GetNumElements(const NP_NeuropodTensor *tensor);

// For string tensors, set the value of a specified element in the flattened tensor
void NP_SetStringElement(NP_NeuropodTensor *tensor, size_t index, const char *item, NP_Status *status);

Expand Down
3 changes: 2 additions & 1 deletion source/neuropod/tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,9 @@ cc_library(
cc_test(
name = "test_c_api",
srcs = [
"test_c_api.cc",
"test_c_api.c",
],
copts = ["-std=c11"],
data = [
"//neuropod/tests/test_data",
],
Expand Down
Loading

0 comments on commit c16ad53

Please sign in to comment.