mirror of
https://github.com/zebrajr/pytorch.git
synced 2025-12-07 00:21:07 +01:00
765 lines
23 KiB
C++
765 lines
23 KiB
C++
#include <Python.h>
|
|
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
|
|
#include <numpy/arrayobject.h>
|
|
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include "caffe2/core/context.h"
|
|
#ifndef PYCAFFE2_CPU_ONLY
|
|
#include "caffe2/core/context_gpu.h"
|
|
#endif // PYCAFFE2_CPU_ONLY
|
|
#include "caffe2/core/init.h"
|
|
#include "caffe2/core/net.h"
|
|
#include "caffe2/core/operator.h"
|
|
#include "caffe2/core/workspace.h"
|
|
#include "caffe2/proto/caffe2.pb.h"
|
|
|
|
//using namespace caffe2; // NOLINT
|
|
using caffe2::Blob;
|
|
using caffe2::DeviceOption;
|
|
using caffe2::Tensor;
|
|
using caffe2::Workspace;
|
|
using caffe2::CPUContext;
|
|
using caffe2::OperatorDef;
|
|
|
|
#ifndef PYCAFFE2_CPU_ONLY
|
|
using caffe2::CUDAContext;
|
|
#endif // PYCAFFE2_CPU_ONLY
|
|
|
|
// gWorkspaces allows us to define and switch between multiple workspaces in
|
|
// Python.
|
|
static std::map<std::string, std::unique_ptr<Workspace> > gWorkspaces;
|
|
// gWorkspace is the pointer to the current workspace. The ownership is kept
|
|
// by the gWorkspaces map.
|
|
static Workspace* gWorkspace = nullptr;
|
|
static std::string gCurrentWorkspaceName;
|
|
|
|
namespace {
|
|
|
|
using caffe2::string;
|
|
|
|
bool SwitchWorkspaceInternal(const string& name, const bool create_if_missing) {
|
|
if (gWorkspaces.count(name)) {
|
|
gCurrentWorkspaceName = name;
|
|
gWorkspace = gWorkspaces[name].get();
|
|
return true;
|
|
} else if (create_if_missing) {
|
|
std::unique_ptr<Workspace> new_workspace(new Workspace());
|
|
gWorkspace = new_workspace.get();
|
|
gWorkspaces.insert(std::make_pair(name, std::move(new_workspace)));
|
|
gCurrentWorkspaceName = name;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
inline string PyBytesToStdString(PyObject* pystring) {
|
|
return string(PyBytes_AsString(pystring), PyBytes_Size(pystring));
|
|
}
|
|
|
|
inline PyObject* StdStringToPyBytes(const string& str) {
|
|
return PyBytes_FromStringAndSize(str.c_str(), str.size());
|
|
}
|
|
|
|
template <typename T>
|
|
inline void MakeStringInternal(std::stringstream& ss, const T& t) {
|
|
ss << t;
|
|
}
|
|
|
|
template <typename T, typename ... Args>
|
|
inline void MakeStringInternal(std::stringstream& ss, const T& t, const Args&... args) {
|
|
MakeStringInternal(ss, t);
|
|
MakeStringInternal(ss, args...);
|
|
}
|
|
|
|
template <typename... Args>
|
|
string MakeString(const Args&... args) {
|
|
std::stringstream ss;
|
|
MakeStringInternal(ss, args...);
|
|
return string(ss.str());
|
|
}
|
|
|
|
|
|
inline void PyErr_SetString(PyObject* type, const string& str) {
|
|
PyErr_SetString(type, str.c_str());
|
|
}
|
|
|
|
static_assert(sizeof(int) == sizeof(int32_t),
|
|
"Yangqing made a loose assumption that int will always be int32 "
|
|
"for numpy type mapping");
|
|
|
|
template <typename T> struct NumpyTypeWrapper;
|
|
template<> struct NumpyTypeWrapper<float> {
|
|
static const int type = NPY_FLOAT;
|
|
};
|
|
template<> struct NumpyTypeWrapper<int> {
|
|
static const int type = NPY_INT32;
|
|
};
|
|
|
|
template <typename T, class DeviceContext>
|
|
PyObject* FetchTensor(const Tensor<DeviceContext>& tensor) {
|
|
DeviceContext context;
|
|
CAFFE_CHECK_GT(tensor.size(), 0);
|
|
std::vector<npy_intp> npy_dims;
|
|
for (const int dim : tensor.dims()) {
|
|
npy_dims.push_back(dim);
|
|
}
|
|
PyObject* array = PyArray_SimpleNew(
|
|
tensor.ndim(), npy_dims.data(), NumpyTypeWrapper<T>::type);
|
|
// Now, copy the data to the tensor.
|
|
// TODO(Yangqing): Is there an easier way to convert PyObject to
|
|
// PyArrayObject?
|
|
context.template Copy<T, DeviceContext, caffe2::CPUContext>(
|
|
tensor.size(), tensor.template data<T>(),
|
|
static_cast<T*>(PyArray_DATA(reinterpret_cast<PyArrayObject*>(array))));
|
|
context.FinishDeviceComputation();
|
|
return array;
|
|
}
|
|
|
|
template <typename T, class DeviceContext>
|
|
PyObject* FeedTensor(const DeviceOption& option, PyArrayObject* original_array,
|
|
Blob* blob) {
|
|
PyArrayObject* array = PyArray_GETCONTIGUOUS(original_array);
|
|
DeviceContext context(option);
|
|
Tensor<DeviceContext>* tensor =
|
|
blob->GetMutable<Tensor<DeviceContext> >();
|
|
// numpy requires long int as its dims.
|
|
int ndim = PyArray_NDIM(array);
|
|
npy_intp* npy_dims = PyArray_DIMS(array);
|
|
std::vector<int> dims;
|
|
for (int i = 0; i < ndim; ++i) {
|
|
dims.push_back(npy_dims[i]);
|
|
}
|
|
tensor->Reshape(dims);
|
|
// Now, copy the data to the tensor.
|
|
context.template Copy<T, caffe2::CPUContext, DeviceContext>(
|
|
tensor->size(), static_cast<T*>(PyArray_DATA(array)),
|
|
tensor->template mutable_data<T>());
|
|
context.FinishDeviceComputation();
|
|
Py_XDECREF(array);
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
PyObject* GlobalInit(PyObject* self, PyObject* args) {
|
|
static bool global_init_called = false;
|
|
if (global_init_called) {
|
|
PyErr_SetString(PyExc_RuntimeError, "GlobalInit already called.");
|
|
return NULL;
|
|
}
|
|
PyObject* list;
|
|
if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &list)) {
|
|
PyErr_SetString(PyExc_ValueError, "Incorrect arguments.");
|
|
return NULL;
|
|
}
|
|
int argc = PyList_Size(list);
|
|
std::unique_ptr<char*> argv(new char*[std::max(argc, 1)]);
|
|
char** raw_argv = argv.get();
|
|
for (int i = 0; i < argc; ++i) {
|
|
// Get the pointer to the string
|
|
raw_argv[i] = PyBytes_AsString(PyList_GetItem(list, i));
|
|
}
|
|
// Special case for argc = 0: in this case, we will simply add a dummy
|
|
// argv to call caffe2's underlying code.
|
|
if (argc == 0) {
|
|
++argc;
|
|
raw_argv[0] = "python";
|
|
}
|
|
global_init_called = true;
|
|
if (!caffe2::GlobalInit(&argc, raw_argv)) {
|
|
PyErr_SetString(PyExc_RuntimeError, "Error in global init.");
|
|
return NULL;
|
|
}
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
PyObject* RegisteredOperators(PyObject* self, PyObject* args) {
|
|
std::set<string> all_keys;
|
|
// CPU operators
|
|
for (const auto& name : caffe2::CPUOperatorRegistry()->Keys()) {
|
|
all_keys.insert(name);
|
|
}
|
|
// CUDA operators
|
|
for (const auto& name : caffe2::CUDAOperatorRegistry()->Keys()) {
|
|
all_keys.insert(name);
|
|
}
|
|
// Now, add it to the list
|
|
PyObject* list = PyList_New(all_keys.size());
|
|
int idx = 0;
|
|
for (const string& name : all_keys) {
|
|
CAFFE_CHECK_EQ(PyList_SetItem(list, idx, StdStringToPyBytes(name)), 0);
|
|
++idx;
|
|
}
|
|
return list;
|
|
}
|
|
|
|
PyObject* GetGradientDefs(PyObject* self, PyObject* args) {
|
|
PyObject* def_string = nullptr;
|
|
if (!PyArg_ParseTuple(args, "|S", &def_string)) {
|
|
PyErr_SetString(PyExc_ValueError,
|
|
"GetGradientDefs requires an input that is a serialized "
|
|
"OperatorDef protobuffer.");
|
|
return NULL;
|
|
}
|
|
OperatorDef def;
|
|
if (!def.ParseFromString(PyBytesToStdString(def_string))) {
|
|
PyErr_SetString(PyExc_ValueError,
|
|
"Provided string is not a valid OperatorDef protobuffer.");
|
|
return NULL;
|
|
}
|
|
std::unique_ptr<std::vector<OperatorDef> > grad_defs(GetGradientDefs(def));
|
|
if (grad_defs.get() == nullptr) {
|
|
PyErr_SetString(
|
|
PyExc_ValueError,
|
|
("Gradient not registered for operator type " + def.type()).c_str());
|
|
return NULL;
|
|
}
|
|
PyObject* list = PyList_New(grad_defs->size());
|
|
int i = 0;
|
|
for (const OperatorDef & grad_def : *grad_defs) {
|
|
CAFFE_CHECK_EQ(PyList_SetItem(
|
|
list, i, StdStringToPyBytes(grad_def.SerializeAsString())), 0);
|
|
++i;
|
|
}
|
|
return list;
|
|
}
|
|
|
|
PyObject* SwitchWorkspace(PyObject* self, PyObject* args) {
|
|
PyObject* name = nullptr;
|
|
PyObject* create_if_missing = nullptr;
|
|
if (!PyArg_ParseTuple(args, "S|O", &name, &create_if_missing)) {
|
|
PyErr_SetString(PyExc_ValueError,
|
|
"SwitchWorkspace takes in a workspace name, and "
|
|
"an optional boolean value that specifies whether "
|
|
"we want to create the workspace if it is missing.");
|
|
return NULL;
|
|
}
|
|
bool success = SwitchWorkspaceInternal(
|
|
PyBytesToStdString(name),
|
|
(create_if_missing != nullptr) && PyObject_IsTrue(create_if_missing));
|
|
if (!success) {
|
|
PyErr_SetString(
|
|
PyExc_RuntimeError,
|
|
"Workspace of the given name does not exist, and I am not instructed "
|
|
"to create it either.");
|
|
return NULL;
|
|
}
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
PyObject* CurrentWorkspace(PyObject* self, PyObject* args) {
|
|
return StdStringToPyBytes(gCurrentWorkspaceName);
|
|
}
|
|
|
|
PyObject* Workspaces(PyObject* self, PyObject* args) {
|
|
PyObject* list = PyList_New(gWorkspaces.size());
|
|
int i = 0;
|
|
for (auto const & it : gWorkspaces) {
|
|
CAFFE_CHECK_EQ(PyList_SetItem(list, i, StdStringToPyBytes(it.first)), 0);
|
|
i += 1;
|
|
}
|
|
return list;
|
|
}
|
|
|
|
PyObject* ResetWorkspace(PyObject* self, PyObject* args) {
|
|
PyObject* root_folder = nullptr;
|
|
if (!PyArg_ParseTuple(args, "|S", &root_folder)) {
|
|
PyErr_SetString(PyExc_ValueError,
|
|
"ResetWorkspace takes in either no argument, or a string "
|
|
"specifying the root folder of the workspace.");
|
|
return NULL;
|
|
}
|
|
CAFFE_VLOG(1) << "Resetting workspace.";
|
|
if (root_folder == nullptr) {
|
|
gWorkspaces[gCurrentWorkspaceName].reset(
|
|
new Workspace());
|
|
} else {
|
|
gWorkspaces[gCurrentWorkspaceName].reset(
|
|
new Workspace(PyBytesToStdString(root_folder)));
|
|
}
|
|
gWorkspace = gWorkspaces[gCurrentWorkspaceName].get();
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
PyObject* RootFolder(PyObject* self, PyObject* args) {
|
|
return StdStringToPyBytes(gWorkspace->RootFolder());
|
|
}
|
|
|
|
// This function should not be called by the user - only used during the
|
|
// destruction of the module.
|
|
PyObject* OnModuleExit(PyObject* self, PyObject* args) {
|
|
gWorkspaces.clear();
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
PyObject* Blobs(PyObject* self, PyObject* args) {
|
|
std::vector<caffe2::string> blob_strings = gWorkspace->Blobs();
|
|
PyObject* list = PyList_New(blob_strings.size());
|
|
for (int i = 0; i < blob_strings.size(); ++i) {
|
|
CAFFE_CHECK_EQ(
|
|
PyList_SetItem(list, i, StdStringToPyBytes(blob_strings[i])), 0);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
PyObject* HasBlob(PyObject* self, PyObject* args) {
|
|
char* name;
|
|
if (!PyArg_ParseTuple(args, "s", &name)) {
|
|
return NULL;
|
|
}
|
|
if (gWorkspace->HasBlob(caffe2::string(name))) {
|
|
Py_RETURN_TRUE;
|
|
} else {
|
|
Py_RETURN_FALSE;
|
|
}
|
|
}
|
|
|
|
PyObject* CreateNet(PyObject* self, PyObject* args) {
|
|
PyObject* proto_string;
|
|
if (!PyArg_ParseTuple(args, "S", &proto_string)) {
|
|
return NULL;
|
|
}
|
|
caffe2::NetDef proto;
|
|
if (!proto.ParseFromString(PyBytesToStdString(proto_string))) {
|
|
PyErr_SetString(PyExc_ValueError, "Cannot parse input net string.");
|
|
return NULL;
|
|
}
|
|
if (!gWorkspace->CreateNet(proto)) {
|
|
PyErr_SetString(
|
|
PyExc_RuntimeError,
|
|
"Cannot create network. See console log for error messages.");
|
|
return NULL;
|
|
}
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
PyObject* RunNet(PyObject* self, PyObject* args) {
|
|
char* name;
|
|
if (!PyArg_ParseTuple(args, "s", &name)) {
|
|
PyErr_SetString(PyExc_ValueError,
|
|
"Incorrect argument. Must pass in a single string.");
|
|
return NULL;
|
|
}
|
|
if (!gWorkspace->RunNet(caffe2::string(name))) {
|
|
PyErr_SetString(
|
|
PyExc_RuntimeError,
|
|
"Cannot run network. See console log for error messages.");
|
|
return NULL;
|
|
}
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
|
|
PyObject* BenchmarkNet(PyObject* self, PyObject* args) {
|
|
char* name;
|
|
int warmup_runs = 0;
|
|
int main_runs = 0;
|
|
PyObject* run_individual = nullptr;
|
|
if (!PyArg_ParseTuple(args, "siiO", &name, &warmup_runs,
|
|
&main_runs, &run_individual)) {
|
|
PyErr_SetString(PyExc_ValueError,
|
|
"Incorrect argument.");
|
|
return NULL;
|
|
}
|
|
caffe2::NetBase* net = gWorkspace->GetNet(caffe2::string(name));
|
|
if (net == nullptr) {
|
|
PyErr_SetString(PyExc_RuntimeError, "Cannot find network.");
|
|
return NULL;
|
|
}
|
|
net->TEST_Benchmark(warmup_runs, main_runs,
|
|
PyObject_IsTrue(run_individual));
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
PyObject* DeleteNet(PyObject* self, PyObject* args) {
|
|
char* name;
|
|
if (!PyArg_ParseTuple(args, "s", &name)) {
|
|
PyErr_SetString(PyExc_ValueError,
|
|
"Incorrect argument. Must pass in a single string.");
|
|
return NULL;
|
|
}
|
|
gWorkspace->DeleteNet(caffe2::string(name));
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
PyObject* Nets(PyObject* self, PyObject* args) {
|
|
std::vector<caffe2::string> net_strings = gWorkspace->Nets();
|
|
PyObject* list = PyList_New(net_strings.size());
|
|
for (int i = 0; i < net_strings.size(); ++i) {
|
|
CAFFE_CHECK_EQ(PyList_SetItem(list, i, StdStringToPyBytes(net_strings[i])), 0);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
PyObject* RunOperatorOnce(PyObject* self, PyObject* args) {
|
|
PyObject* proto_string;
|
|
if (!PyArg_ParseTuple(args, "S", &proto_string)) {
|
|
PyErr_SetString(PyExc_ValueError,
|
|
"Incorrect argument. Must pass in a single string.");
|
|
return NULL;
|
|
}
|
|
caffe2::OperatorDef proto;
|
|
if (!proto.ParseFromString(PyBytesToStdString(proto_string))) {
|
|
PyErr_SetString(PyExc_ValueError, "Cannot parse input operator proto.");
|
|
return NULL;
|
|
}
|
|
if (!gWorkspace->RunOperatorOnce(proto)) {
|
|
PyErr_SetString(
|
|
PyExc_RuntimeError,
|
|
"Cannot run operator. See console log for error messages.");
|
|
return NULL;
|
|
}
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
PyObject* RunNetOnce(PyObject* self, PyObject* args) {
|
|
PyObject* proto_string;
|
|
if (!PyArg_ParseTuple(args, "S", &proto_string)) {
|
|
PyErr_SetString(PyExc_ValueError,
|
|
"Incorrect argument. Must pass in a single string.");
|
|
return NULL;
|
|
}
|
|
caffe2::NetDef proto;
|
|
if (!proto.ParseFromString(PyBytesToStdString(proto_string))) {
|
|
PyErr_SetString(PyExc_ValueError, "Cannot parse input net proto.");
|
|
return NULL;
|
|
}
|
|
if (!gWorkspace->RunNetOnce(proto)) {
|
|
PyErr_SetString(
|
|
PyExc_RuntimeError,
|
|
"Cannot run net. See console log for error messages.");
|
|
return NULL;
|
|
}
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
PyObject* RunPlan(PyObject* self, PyObject* args) {
|
|
PyObject* proto_string;
|
|
if (!PyArg_ParseTuple(args, "S", &proto_string)) {
|
|
PyErr_SetString(PyExc_ValueError,
|
|
"Incorrect argument. Must pass in a single string.");
|
|
return NULL;
|
|
}
|
|
caffe2::PlanDef proto;
|
|
if (!proto.ParseFromString(PyBytesToStdString(proto_string))) {
|
|
PyErr_SetString(PyExc_ValueError, "Cannot parse input plan proto.");
|
|
return NULL;
|
|
}
|
|
if (!gWorkspace->RunPlan(proto)) {
|
|
PyErr_SetString(
|
|
PyExc_RuntimeError,
|
|
"Cannot run plan. See console log for error messages.");
|
|
return NULL;
|
|
}
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
PyObject* CreateBlob(PyObject* self, PyObject* args) {
|
|
char* name_char;
|
|
if (!PyArg_ParseTuple(args, "s", &name_char)) {
|
|
PyErr_SetString(PyExc_ValueError, "Incorrect arguments.");
|
|
return NULL;
|
|
}
|
|
caffe2::string name(name_char);
|
|
(void) gWorkspace->CreateBlob(name);
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
PyObject* FetchBlob(PyObject* self, PyObject* args) {
|
|
char* name;
|
|
if (!PyArg_ParseTuple(args, "s", &name)) {
|
|
PyErr_SetString(PyExc_ValueError, "Incorrect arguments.");
|
|
return NULL;
|
|
}
|
|
if (!gWorkspace->HasBlob(caffe2::string(name))) {
|
|
PyErr_SetString(PyExc_ValueError, "Requested blob does not exist.");
|
|
return NULL;
|
|
}
|
|
const caffe2::Blob& blob = *(gWorkspace->GetBlob(caffe2::string(name)));
|
|
if (blob.IsType<Tensor<CPUContext> >()) {
|
|
const Tensor<CPUContext>& tensor = blob.Get<Tensor<CPUContext> >();
|
|
if (tensor.IsType<float>()) {
|
|
return FetchTensor<float, CPUContext>(tensor);
|
|
} else if (tensor.IsType<int>()) {
|
|
return FetchTensor<int, CPUContext>(tensor);
|
|
}
|
|
}
|
|
#ifndef PYCAFFE2_CPU_ONLY
|
|
if (blob.IsType<Tensor<CUDAContext> >()) {
|
|
const Tensor<CUDAContext>& tensor = blob.Get<Tensor<CUDAContext> >();
|
|
if (tensor.IsType<float>()) {
|
|
return FetchTensor<float, CUDAContext>(tensor);
|
|
} else if (tensor.IsType<int>()) {
|
|
return FetchTensor<int, CUDAContext>(tensor);
|
|
}
|
|
}
|
|
#endif // !PYCAFFE2_CPU_ONLY
|
|
// If all branches failed, we will return a metainfo string.
|
|
std::stringstream ss;
|
|
ss << caffe2::string(name) << ", a C++ native class of type "
|
|
<< blob.TypeName() << ".";
|
|
return StdStringToPyBytes(ss.str());
|
|
}
|
|
|
|
PyObject* FeedBlob(PyObject* self, PyObject* args) {
|
|
char* name_char;
|
|
PyArrayObject* array = nullptr;
|
|
PyObject* device_option_string = nullptr;
|
|
if (!PyArg_ParseTuple(args, "sO!|O", &name_char, &PyArray_Type, &array,
|
|
&device_option_string)) {
|
|
PyErr_SetString(PyExc_ValueError, "Incorrect arguments.");
|
|
return NULL;
|
|
}
|
|
caffe2::string name(name_char);
|
|
DeviceOption option;
|
|
if (device_option_string != nullptr) {
|
|
// If we have a device option passed in, read it.
|
|
if (!option.ParseFromString(PyBytesToStdString(device_option_string))) {
|
|
PyErr_SetString(PyExc_ValueError, "Cannot parse device option string.");
|
|
return NULL;
|
|
}
|
|
}
|
|
Blob* blob = gWorkspace->CreateBlob(name);
|
|
int data_type = PyArray_TYPE(array);
|
|
|
|
// Since there is really no polymorphism, we will have to do so...
|
|
switch (option.device_type()) {
|
|
case caffe2::CPU:
|
|
switch (data_type) {
|
|
case NPY_LONG:
|
|
if (sizeof(long) != sizeof(int)) {
|
|
CAFFE_LOG_FATAL << "On this platform NPY_LONG does not equal to "
|
|
"NPY_INT and such type is not supported yet.";
|
|
} else {
|
|
return FeedTensor<int, caffe2::CPUContext>(option, array, blob);
|
|
}
|
|
case NPY_INT:
|
|
return FeedTensor<int, caffe2::CPUContext>(option, array, blob);
|
|
case NPY_FLOAT:
|
|
return FeedTensor<float, caffe2::CPUContext>(option, array, blob);
|
|
default:
|
|
PyErr_SetString(PyExc_TypeError,
|
|
MakeString("Unsupported numpy data type: ", data_type, "."));
|
|
return NULL;
|
|
}
|
|
#ifndef PYCAFFE2_CPU_ONLY
|
|
case caffe2::CUDA:
|
|
switch (data_type) {
|
|
case NPY_LONG:
|
|
if (sizeof(long) != sizeof(int)) {
|
|
CAFFE_LOG_FATAL << "On this platform NPY_LONG does not equal to "
|
|
"NPY_INT and such type is not supported yet.";
|
|
} else {
|
|
return FeedTensor<int, caffe2::CUDAContext>(option, array, blob);
|
|
}
|
|
case NPY_INT:
|
|
return FeedTensor<int, caffe2::CUDAContext>(option, array, blob);
|
|
case NPY_FLOAT:
|
|
return FeedTensor<float, caffe2::CUDAContext>(option, array, blob);
|
|
default:
|
|
PyErr_SetString(PyExc_TypeError,
|
|
MakeString("Unsupported numpy data type: ", data_type, "."));
|
|
return NULL;
|
|
}
|
|
#endif // !PYCAFFE2_CPU_ONLY
|
|
default:
|
|
PyErr_SetString(PyExc_TypeError, "Unknown device type.");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
PyObject* HasGPUSupport(PyObject* self, PyObject* args) {
|
|
#ifdef PYCAFFE2_CPU_ONLY
|
|
return Py_BuildValue("i", 0);
|
|
#else // PYCAFFE2_CPU_ONLY
|
|
return Py_BuildValue("i", 1);
|
|
#endif // PYCAFFE2_CPU_ONLY
|
|
}
|
|
|
|
#ifndef PYCAFFE2_CPU_ONLY
|
|
// Here are functions that are purely GPU-based functions to be filled.
|
|
|
|
PyObject* NumberOfGPUs(PyObject* self, PyObject* args) {
|
|
int num_devices = 0;
|
|
auto err = cudaGetDeviceCount(&num_devices);
|
|
if (err == cudaErrorNoDevice || err == cudaErrorInsufficientDriver) {
|
|
return Py_BuildValue("i", 0);
|
|
} else if (err != cudaSuccess) {
|
|
PyErr_SetString(PyExc_RuntimeError, "Runtime CUDA error.");
|
|
return NULL;
|
|
}
|
|
return Py_BuildValue("i", num_devices);
|
|
}
|
|
|
|
PyObject* SetDefaultGPUID(PyObject* self, PyObject* args) {
|
|
int device_id;
|
|
if (!PyArg_ParseTuple(args, "i", &device_id)) {
|
|
PyErr_SetString(PyExc_ValueError, "Incorrect arguments: must pass an int.");
|
|
return NULL;
|
|
}
|
|
caffe2::SetDefaultGPUID(device_id);
|
|
Py_RETURN_TRUE;
|
|
}
|
|
|
|
PyObject* GetDefaultGPUID(PyObject* self, PyObject* args) {
|
|
int device_id = caffe2::GetDefaultGPUID();
|
|
return Py_BuildValue("i", device_id);
|
|
}
|
|
|
|
PyObject* GetCudaPeerAccessPattern(PyObject* self, PyObject* args) {
|
|
std::vector<std::vector<bool> > pattern;
|
|
if (!caffe2::GetCudaPeerAccessPattern(&pattern)) {
|
|
PyErr_SetString(PyExc_RuntimeError,
|
|
"Error in running caffe2::GetCudaPeerAccessPattern.");
|
|
return NULL;
|
|
}
|
|
std::vector<npy_intp> npy_dims;
|
|
int num_devices = pattern.size();
|
|
npy_dims.push_back(num_devices);
|
|
npy_dims.push_back(num_devices);
|
|
|
|
PyObject* array = PyArray_SimpleNew(2, npy_dims.data(), NPY_BOOL);
|
|
bool* npy_data = static_cast<bool*>(
|
|
PyArray_DATA(reinterpret_cast<PyArrayObject*>(array)));
|
|
for (int i = 0; i < num_devices; ++i) {
|
|
for (int j = 0; j < num_devices; ++j) {
|
|
*(npy_data++) = pattern[i][j];
|
|
}
|
|
}
|
|
return array;
|
|
}
|
|
|
|
#endif // !PYCAFFE2_CPU_ONLY
|
|
|
|
// A simple macro to avoid writing repeated symbols.
|
|
#define _PYNAME(name) {#name, name, METH_VARARGS, ""}
|
|
static PyMethodDef gPycaffe2Methods[] = {
|
|
// TODO(Yangqing): write the methods string.
|
|
// Note(Yangqing): For any function that we are going to override in the
|
|
// python file, we prepend "cc_" here.
|
|
_PYNAME(GlobalInit),
|
|
_PYNAME(RegisteredOperators),
|
|
{"cc_GetGradientDefs", GetGradientDefs, METH_VARARGS, ""},
|
|
_PYNAME(SwitchWorkspace),
|
|
_PYNAME(CurrentWorkspace),
|
|
_PYNAME(Workspaces),
|
|
{"cc_ResetWorkspace", ResetWorkspace, METH_VARARGS, ""},
|
|
_PYNAME(RootFolder),
|
|
_PYNAME(OnModuleExit),
|
|
_PYNAME(Blobs),
|
|
_PYNAME(HasBlob),
|
|
{"cc_CreateNet", CreateNet, METH_VARARGS, ""},
|
|
_PYNAME(RunNet),
|
|
_PYNAME(BenchmarkNet),
|
|
_PYNAME(DeleteNet),
|
|
_PYNAME(Nets),
|
|
{"cc_RunOperatorOnce", RunOperatorOnce, METH_VARARGS, ""},
|
|
{"cc_RunNetOnce", RunNetOnce, METH_VARARGS, ""},
|
|
{"cc_RunPlan", RunPlan, METH_VARARGS, ""},
|
|
_PYNAME(CreateBlob),
|
|
_PYNAME(FetchBlob),
|
|
{"cc_FeedBlob", FeedBlob, METH_VARARGS, ""},
|
|
_PYNAME(HasGPUSupport),
|
|
#ifndef PYCAFFE2_CPU_ONLY
|
|
_PYNAME(NumberOfGPUs),
|
|
_PYNAME(SetDefaultGPUID),
|
|
_PYNAME(GetDefaultGPUID),
|
|
_PYNAME(GetCudaPeerAccessPattern),
|
|
#endif // !PYCAFFE2_CPU_ONLY
|
|
{NULL, NULL, 0, NULL}, // end of python methods.
|
|
};
|
|
#undef _PYNAME
|
|
|
|
// This is a workaround so we can deal with numpy's import_array behavior.
|
|
// Despite the fact that you may think import_array() is a function call,
|
|
// it seems that (as of 1.10) that is defined as a macro. As a result, we
|
|
// wrap it inside a function to make everythings safe, as well as checking
|
|
// the different behaviors in python 2 and 3.
|
|
#if PY_MAJOR_VERSION >= 3
|
|
#define CAFFE2_NUMPY_RETURN_TYPE int
|
|
#else
|
|
#define CAFFE2_NUMPY_RETURN_TYPE void
|
|
#endif
|
|
|
|
CAFFE2_NUMPY_RETURN_TYPE caffe2_init_numpy_wrapper() {
|
|
import_array();
|
|
}
|
|
|
|
void common_init_libcaffe2_python(void) {
|
|
caffe2_init_numpy_wrapper(); // for numpy
|
|
// We will create a default workspace for us to run stuff.
|
|
SwitchWorkspaceInternal("default", true);
|
|
gCurrentWorkspaceName = "default";
|
|
}
|
|
|
|
// The initialization code.
|
|
#if PY_MAJOR_VERSION >= 3
|
|
|
|
struct module_state {
|
|
PyObject* error;
|
|
};
|
|
|
|
inline static struct module_state* ModuleGetState(PyObject* module) {
|
|
return (struct module_state*)PyModule_GetState(module);
|
|
}
|
|
static int caffe2_python_traverse(PyObject* m, visitproc visit, void* arg) {
|
|
Py_VISIT(ModuleGetState(m)->error);
|
|
return 0;
|
|
}
|
|
|
|
static int caffe2_python_clear(PyObject* m) {
|
|
Py_CLEAR(ModuleGetState(m)->error);
|
|
return 0;
|
|
}
|
|
|
|
static struct PyModuleDef gModuleDef = {
|
|
PyModuleDef_HEAD_INIT,
|
|
"libcaffe2_python",
|
|
NULL,
|
|
sizeof(struct module_state),
|
|
gPycaffe2Methods,
|
|
NULL,
|
|
caffe2_python_traverse,
|
|
caffe2_python_clear,
|
|
NULL
|
|
};
|
|
|
|
PyObject* PyInit_libcaffe2_python(void) {
|
|
PyObject* module = PyModule_Create(&gModuleDef);
|
|
if (module == nullptr) {
|
|
return NULL;
|
|
}
|
|
struct module_state* st = ModuleGetState(module);
|
|
st->error = PyErr_NewException("libcaffe2_python.Error", NULL, NULL);
|
|
if (st->error == NULL) {
|
|
Py_DECREF(module);
|
|
return NULL;
|
|
}
|
|
return module;
|
|
common_init_libcaffe2_python();
|
|
}
|
|
|
|
#else // PY_MAJOR_VERSION >= 3
|
|
|
|
void initlibcaffe2_python(void) {
|
|
PyObject* module = Py_InitModule("libcaffe2_python", gPycaffe2Methods);
|
|
if (module == nullptr) {
|
|
return;
|
|
}
|
|
common_init_libcaffe2_python();
|
|
}
|
|
|
|
#endif // PY_MAJOR_VERSION >= 3
|
|
|
|
} // extern "C"
|
|
|