source/neuropod/bindings/neuropod_native.cc (136 lines of code) (raw):
/* Copyright (c) 2020 The Neuropod Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "neuropod/bindings/python_bindings.hh"
#include "neuropod/core/generic_tensor.hh"
#include "neuropod/neuropod.hh"
#include "neuropod/serialization/serialization.hh"
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <sstream>
namespace neuropod
{
namespace
{
// A mapping between numpy types and Neuropod types
// TODO(vip): Share this with config_utils.cc
const std::unordered_map<std::string, TensorType> type_mapping = {
{"float32", FLOAT_TENSOR},
{"float64", DOUBLE_TENSOR},
{"string", STRING_TENSOR},
{"int8", INT8_TENSOR},
{"int16", INT16_TENSOR},
{"int32", INT32_TENSOR},
{"int64", INT64_TENSOR},
{"uint8", UINT8_TENSOR},
{"uint16", UINT16_TENSOR},
{"uint32", UINT32_TENSOR},
{"uint64", UINT64_TENSOR},
};
py::dict infer(Neuropod &neuropod, py::dict &inputs_dict)
{
// Convert from a py::dict of numpy arrays to an unordered_map of `NeuropodTensor`s
auto allocator = neuropod.get_tensor_allocator();
NeuropodValueMap inputs = from_numpy_dict(*allocator, inputs_dict);
// Run inference
auto outputs = neuropod.infer(inputs);
// Convert the outputs to a python dict of numpy arrays
return to_numpy_dict(*outputs);
}
py::array deserialize_tensor_binding(py::bytes buffer)
{
// Deserialize to a NeuropodTensor
std::istringstream input_stream(buffer);
auto allocator = get_generic_tensor_allocator();
auto val = deserialize<std::shared_ptr<NeuropodValue>>(input_stream, *allocator);
// Wrap it in a numpy array and return
return tensor_to_numpy(std::dynamic_pointer_cast<NeuropodTensor>(val));
}
py::bytes serialize_tensor_binding(py::array numpy_array)
{
// Wrap the numpy array in a NeuropodTensor
auto allocator = get_generic_tensor_allocator();
auto tensor = tensor_from_numpy(*allocator, numpy_array);
// Serialize the tensor
std::stringstream buffer_stream;
serialize(buffer_stream, *tensor);
return py::bytes(buffer_stream.str());
}
py::dict deserialize_valuemap_binding(py::bytes buffer)
{
// Deserialize to a NeuropodTensor
std::istringstream input_stream(buffer);
auto allocator = get_generic_tensor_allocator();
auto val = deserialize<NeuropodValueMap>(input_stream, *allocator);
// Wrap it in a numpy array and return
return to_numpy_dict(val);
}
py::bytes serialize_valuemap_binding(py::dict items)
{
// Wrap the numpy array in a NeuropodTensor
auto allocator = get_generic_tensor_allocator();
auto valuemap = from_numpy_dict(*allocator, items);
// Serialize the tensor
std::stringstream buffer_stream;
serialize(buffer_stream, valuemap);
return py::bytes(buffer_stream.str());
}
RuntimeOptions get_options_from_kwargs(py::kwargs &kwargs)
{
RuntimeOptions options;
for (const auto &item : kwargs)
{
const auto key = item.first.cast<std::string>();
const auto &value = item.second;
if (key == "visible_gpu")
{
if (value.is_none())
{
options.visible_device = Device::CPU;
}
else
{
options.visible_device = value.cast<int>();
}
}
else if (key == "use_ope")
{
options.use_ope = value.cast<bool>();
}
else
{
NEUROPOD_ERROR("Got unexpected keyword argument {}", key);
}
}
return options;
}
template <typename... Params>
std::unique_ptr<Neuropod> make_neuropod(py::kwargs kwargs, Params &&... params)
{
auto options = get_options_from_kwargs(kwargs);
return stdx::make_unique<Neuropod>(std::forward<Params>(params)..., options);
}
} // namespace
PYBIND11_MODULE(neuropod_native, m)
{
py::class_<Neuropod>(m, "Neuropod")
.def(py::init([](const std::string &path, py::kwargs kwargs) { return make_neuropod(kwargs, path); }))
.def(py::init([](const std::string & path,
const std::vector<BackendLoadSpec> &default_backend_overrides,
py::kwargs kwargs) { return make_neuropod(kwargs, path, default_backend_overrides); }))
.def("infer", &infer)
.def("get_inputs", &Neuropod::get_inputs)
.def("get_outputs", &Neuropod::get_outputs)
.def("get_name", &Neuropod::get_name)
.def("get_platform", &Neuropod::get_platform);
py::class_<TensorSpec>(m, "TensorSpec")
.def_readonly("name", &TensorSpec::name)
.def_readonly("type", &TensorSpec::type)
.def_readonly("dims", &TensorSpec::dims);
py::class_<Dimension>(m, "Dimension")
.def_readonly("value", &Dimension::value)
.def_readonly("symbol", &Dimension::symbol);
auto type_enum = py::enum_<TensorType>(m, "TensorType");
for (const auto &item : type_mapping)
{
type_enum = type_enum.value(item.first.c_str(), item.second);
}
py::class_<BackendLoadSpec>(m, "BackendLoadSpec")
.def(py::init<const std::string &, const std::string &, const std::string &>());
m.def("serialize", &serialize_tensor_binding, "Convert a numpy array to a NeuropodTensor and serialize it");
m.def("deserialize",
&deserialize_tensor_binding,
"Deserialize a string of bytes to a NeuropodTensor (and return it as a numpy array)");
m.def("serialize",
&serialize_valuemap_binding,
"Convert a dict of numpy arrays to a NeuropodValueMap and serialize it");
m.def("deserialize_dict",
&deserialize_valuemap_binding,
"Deserialize a string of bytes to a NeuropodValueMap (and return it as a dict of numpy arrays)");
}
} // namespace neuropod