| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| |
| /*! |
| * \file c_api_ndarray.cc |
| * \brief C API of mxnet |
| */ |
| |
| #include <mxnet/base.h> |
| #include <mxnet/c_api.h> |
| #include <mxnet/operator.h> |
| #include <mxnet/operator_util.h> |
| #include <mxnet/op_attr_types.h> |
| #include <mxnet/imperative.h> |
| #include <nnvm/node.h> |
| #include <nnvm/op_attr_types.h> |
| #include <string> |
| #include "./c_api_common.h" |
| #include "../common/utils.h" |
| #include "../common/exec_utils.h" |
| #include "../imperative/imperative_utils.h" |
| #include "../imperative/cached_op.h" |
| #include "../imperative/cached_op_threadsafe.h" |
| #include "../profiler/profiler.h" |
| |
| using namespace mxnet; |
| |
| void SetNDInputsOutputs(const nnvm::Op* op, |
| std::vector<NDArray*>* ndinputs, |
| std::vector<NDArray*>* ndoutputs, |
| int num_inputs, |
| const NDArrayHandle* inputs, |
| int* num_outputs, |
| int infered_num_outputs, |
| int num_visible_outputs, |
| NDArrayHandle** outputs) { |
| NDArray** out_array = *reinterpret_cast<NDArray***>(outputs); |
| |
| ndinputs->clear(); |
| ndinputs->reserve(num_inputs); |
| for (int i = 0; i < num_inputs; ++i) { |
| NDArray* inp = reinterpret_cast<NDArray*>(inputs[i]); |
| if (!features::is_enabled(features::INT64_TENSOR_SIZE)) { |
| if (shape_is_known(inp->shape())) { // Shape may be unknown after dynamic shape operators |
| CHECK_LT(inp->shape().Size(), (int64_t{1} << 31) - 1) |
| << "[SetNDInputsOutputs] Size of tensor you are trying to allocate is larger than " |
| "2^31 elements. Please build with flag USE_INT64_TENSOR_SIZE=1"; |
| } |
| } |
| ndinputs->emplace_back(inp); |
| } |
| |
| ndoutputs->clear(); |
| ndoutputs->reserve(infered_num_outputs); |
| if (out_array == nullptr) { |
| for (int i = 0; i < infered_num_outputs; ++i) { |
| ndoutputs->emplace_back(new NDArray()); |
| } |
| *num_outputs = num_visible_outputs; |
| } else { |
| CHECK(*num_outputs == infered_num_outputs || *num_outputs == num_visible_outputs) |
| << "Operator expects " << infered_num_outputs << " (all) or " << num_visible_outputs |
| << " (visible only) outputs, but got " << *num_outputs << " instead."; |
| for (int i = 0; i < *num_outputs; ++i) { |
| ndoutputs->emplace_back(out_array[i]); |
| } |
| for (int i = *num_outputs; i < infered_num_outputs; ++i) { |
| ndoutputs->emplace_back(new NDArray()); |
| } |
| } |
| } |
| |
| void MXImperativeInvokeImpl(AtomicSymbolCreator creator, |
| int num_inputs, |
| NDArrayHandle* inputs, |
| int* num_outputs, |
| NDArrayHandle** outputs, |
| int num_params, |
| const char** param_keys, |
| const char** param_vals) { |
| const nnvm::Op* op = static_cast<nnvm::Op*>(creator); |
| MXAPIThreadLocalEntry<>* ret = MXAPIThreadLocalStore<>::Get(); |
| |
| nnvm::NodeAttrs attrs = |
| imperative::ParseAttrs(op, num_inputs, num_params, param_keys, param_vals); |
| attrs.dict["__profiler_scope__"] = profiler::ProfilerScope::Get()->GetCurrentProfilerScope(); |
| if (attrs.op) { |
| attrs.name = attrs.op->name; |
| } |
| |
| int infered_num_outputs; |
| int num_visible_outputs; |
| imperative::SetNumOutputs(op, attrs, num_inputs, &infered_num_outputs, &num_visible_outputs); |
| |
| std::vector<NDArray*> ndinputs, ndoutputs; |
| SetNDInputsOutputs(op, |
| &ndinputs, |
| &ndoutputs, |
| num_inputs, |
| inputs, |
| num_outputs, |
| infered_num_outputs, |
| num_visible_outputs, |
| outputs); |
| |
| if (Imperative::Get()->is_deferred_compute()) { |
| Imperative::Get()->RecordDeferredCompute(std::move(attrs), ndinputs, ndoutputs); |
| } else { |
| for (NDArray* input : ndinputs) { |
| Imperative::DCInfo::Compute(*input); |
| } |
| auto state = Imperative::Get()->Invoke(Context::CPU(), attrs, ndinputs, ndoutputs); |
| if (Imperative::Get()->is_recording()) { |
| Imperative::Get()->RecordOp(std::move(attrs), ndinputs, ndoutputs, state); |
| } |
| } |
| |
| for (int i = *num_outputs; i < infered_num_outputs; ++i) |
| delete ndoutputs[i]; |
| |
| if (*outputs == nullptr) { |
| ret->ret_handles.clear(); |
| ret->ret_handles.reserve(*num_outputs); |
| for (int i = 0; i < *num_outputs; ++i) |
| ret->ret_handles.push_back(ndoutputs[i]); |
| *outputs = reinterpret_cast<NDArrayHandle*>(dmlc::BeginPtr(ret->ret_handles)); |
| } |
| } |
| |
| int MXImperativeInvoke(AtomicSymbolCreator creator, |
| int num_inputs, |
| NDArrayHandle* inputs, |
| int* num_outputs, |
| NDArrayHandle** outputs, |
| int num_params, |
| const char** param_keys, |
| const char** param_vals, |
| const int** out_stypes) { // outputs storage types |
| MXAPIThreadLocalEntry<>* ret = MXAPIThreadLocalStore<>::Get(); |
| API_BEGIN(); |
| MXImperativeInvokeImpl( |
| creator, num_inputs, inputs, num_outputs, outputs, num_params, param_keys, param_vals); |
| if (out_stypes != nullptr) { |
| NDArray** out_array = *reinterpret_cast<NDArray***>(outputs); |
| ret->out_types.clear(); |
| ret->out_types.reserve(*num_outputs); |
| for (int i = 0; i < *num_outputs; ++i) { |
| ret->out_types.emplace_back(out_array[i]->storage_type()); |
| } |
| *out_stypes = dmlc::BeginPtr(ret->out_types); |
| } |
| API_END(); |
| } |
| |
| int MXCreateCachedOp(SymbolHandle handle, |
| int num_flags, |
| const char** keys, |
| const char** vals, |
| CachedOpHandle* out, |
| bool thread_safe) { |
| nnvm::Symbol* sym = static_cast<nnvm::Symbol*>(handle); |
| API_BEGIN(); |
| std::vector<std::pair<std::string, std::string> > flags; |
| flags.reserve(num_flags); |
| for (int i = 0; i < num_flags; ++i) { |
| flags.emplace_back(keys[i], vals[i]); |
| } |
| if (!thread_safe) { |
| *out = new CachedOpPtr(new CachedOp(*sym, flags)); |
| } else { |
| *out = new CachedOpPtr(new CachedOpThreadSafe(*sym, flags)); |
| } |
| API_END(); |
| } |
| |
| int MXFreeCachedOp(CachedOpHandle handle) { |
| CachedOpPtr* g = static_cast<CachedOpPtr*>(handle); |
| API_BEGIN(); |
| delete g; |
| API_END(); |
| } |
| |
| /*! |
| * \brief get optimized graph from the cached op |
| */ |
| int MXCachedOpGetOptimizedSymbol(CachedOpHandle handle, SymbolHandle* out) { |
| auto s = new nnvm::Symbol(); |
| API_BEGIN(); |
| CachedOpPtr op = *static_cast<CachedOpPtr*>(handle); |
| *s = op->GetOptimizedSymbol(); |
| *out = s; |
| API_END_HANDLE_ERROR(delete s); |
| } |
| |
| int MXInvokeCachedOp(CachedOpHandle handle, |
| int num_inputs, |
| NDArrayHandle* inputs, |
| int default_dev_type, |
| int default_dev_id, |
| int* num_outputs, |
| NDArrayHandle** outputs, |
| const int** out_stypes) { // outputs storage types |
| MXAPIThreadLocalEntry<>* ret = MXAPIThreadLocalStore<>::Get(); |
| |
| API_BEGIN(); |
| CachedOpPtr op_shared = *static_cast<CachedOpPtr*>(handle); |
| // CachedOp* points to CachedOpThreadSafe object if CreateCachedOpEX |
| // was called with thread_safe=true |
| CachedOp* op = dynamic_cast<CachedOp*>(op_shared.get()); |
| std::vector<NDArray*> ndinputs; |
| ndinputs.reserve(num_inputs); |
| for (int i = 0; i < num_inputs; ++i) { |
| ndinputs.push_back(reinterpret_cast<NDArray*>(inputs[i])); |
| } |
| |
| std::vector<NDArray*> ndoutputs; |
| ndoutputs.reserve(op->num_outputs()); |
| if (*outputs == nullptr) { |
| *num_outputs = op->num_outputs(); |
| for (int i = 0; i < *num_outputs; ++i) |
| ndoutputs.push_back(new NDArray()); |
| } else { |
| CHECK_EQ(*num_outputs, op->num_outputs()) << "CachedOp expects " << op->num_outputs() |
| << " outputs, but " << *num_outputs << " was given."; |
| for (int i = 0; i < *num_outputs; ++i) { |
| ndoutputs.push_back(reinterpret_cast<NDArray*>((*outputs)[i])); |
| } |
| } |
| // construct default context |
| Context ctx = Context::Create(static_cast<Context::DeviceType>(default_dev_type), default_dev_id); |
| op->Forward(op_shared, ndinputs, ndoutputs, ctx); |
| |
| if (*outputs == nullptr) { |
| ret->ret_handles.clear(); |
| ret->ret_handles.reserve(*num_outputs); |
| for (int i = 0; i < *num_outputs; ++i) { |
| ret->ret_handles.push_back(ndoutputs[i]); |
| } |
| *outputs = dmlc::BeginPtr(ret->ret_handles); |
| } |
| if (out_stypes != nullptr) { |
| NDArray** out_array = reinterpret_cast<NDArray**>(*outputs); |
| ret->out_types.clear(); |
| ret->out_types.reserve(*num_outputs); |
| for (int i = 0; i < *num_outputs; ++i) { |
| ret->out_types.emplace_back(out_array[i]->storage_type()); |
| } |
| *out_stypes = dmlc::BeginPtr(ret->out_types); |
| } |
| |
| API_END(); |
| } |
| |
| int MXAutogradIsTraining(bool* curr) { |
| API_BEGIN(); |
| *curr = Imperative::Get()->is_training(); |
| API_END(); |
| } |
| |
| int MXAutogradSetIsTraining(int is_training, int* prev) { |
| API_BEGIN(); |
| *prev = Imperative::Get()->set_is_training(static_cast<bool>(is_training)); |
| API_END(); |
| } |
| |
| int MXAutogradIsRecording(bool* curr) { |
| API_BEGIN(); |
| *curr = Imperative::Get()->is_recording(); |
| API_END(); |
| } |
| |
| int MXAutogradSetIsRecording(int is_recording, int* prev) { |
| API_BEGIN(); |
| *prev = Imperative::Get()->set_is_recording(static_cast<bool>(is_recording)); |
| API_END(); |
| } |
| |
| int MXSetOptimizationConstraints(unsigned int constraints, unsigned int* prev) { |
| API_BEGIN(); |
| *prev = |
| static_cast<unsigned int>(Imperative::Get()->set_opt_constraints(OptConstraint(constraints))); |
| API_END(); |
| } |
| |
| int MXGetOptimizationConstraints(unsigned int* curr) { |
| API_BEGIN(); |
| *curr = static_cast<unsigned int>(Imperative::Get()->get_opt_constraints()); |
| API_END(); |
| } |
| |
| int MXIsNumpyShape(int* curr) { |
| API_BEGIN(); |
| *curr = Imperative::Get()->is_np_shape(); |
| API_END(); |
| } |
| |
| int MXSetIsNumpyShape(int is_np_shape, int* prev) { |
| API_BEGIN(); |
| *prev = Imperative::Get()->set_is_np_shape(is_np_shape); |
| API_END(); |
| } |
| |
| int MXIsNumpyDefaultDtype(bool* curr) { |
| API_BEGIN(); |
| *curr = Imperative::Get()->is_np_default_dtype(); |
| API_END(); |
| } |
| |
| int MXSetIsNumpyDefaultDtype(bool default_dtype, bool* prev) { |
| API_BEGIN(); |
| *prev = Imperative::Get()->set_is_np_default_dtype(default_dtype); |
| API_END(); |
| } |
| |
| int MXAutogradMarkVariables(uint32_t num_var, |
| NDArrayHandle* var_handles, |
| uint32_t* reqs_array, |
| NDArrayHandle* grad_handles) { |
| API_BEGIN(); |
| std::vector<NDArray*> variables, gradients; |
| std::vector<uint32_t> grad_reqs; |
| variables.reserve(num_var); |
| gradients.reserve(num_var); |
| grad_reqs.reserve(num_var); |
| for (uint32_t i = 0; i < num_var; ++i) { |
| variables.emplace_back(static_cast<NDArray*>(var_handles[i])); |
| gradients.emplace_back(static_cast<NDArray*>(grad_handles[i])); |
| grad_reqs.emplace_back(reqs_array[i]); |
| } |
| Imperative::Get()->MarkVariables(variables, grad_reqs, gradients); |
| API_END(); |
| } |
| |
| int MXAutogradDropGrads(uint32_t num_var, NDArrayHandle* var_handles) { |
| API_BEGIN(); |
| std::vector<NDArray*> variables; |
| variables.reserve(num_var); |
| for (uint32_t i = 0; i < num_var; ++i) { |
| variables.emplace_back(static_cast<NDArray*>(var_handles[i])); |
| } |
| Imperative::Get()->DropGrads(variables); |
| API_END(); |
| } |
| |
| int MXAutogradComputeGradient(uint32_t num_output, NDArrayHandle* output_handles) { |
| return MXAutogradBackward(num_output, output_handles, nullptr, 0); |
| } |
| |
| int MXAutogradBackward(uint32_t num_output, |
| NDArrayHandle* output_handles, |
| NDArrayHandle* ograd_handles, |
| int retain_graph) { |
| return MXAutogradBackwardEx(num_output, |
| output_handles, |
| ograd_handles, |
| 0, |
| nullptr, |
| retain_graph, |
| false, |
| true, |
| nullptr, |
| nullptr); |
| } |
| |
| int MXAutogradBackwardEx(uint32_t num_output, |
| NDArrayHandle* output_handles, |
| NDArrayHandle* ograd_handles, |
| uint32_t num_variables, |
| NDArrayHandle* var_handles, |
| int retain_graph, |
| int create_graph, |
| int is_train, |
| NDArrayHandle** grad_handles, |
| int** grad_stypes) { |
| MXAPIThreadLocalEntry<>* ret = MXAPIThreadLocalStore<>::Get(); |
| API_BEGIN(); |
| |
| std::vector<NDArray*> outputs, ograds, variables; |
| outputs.reserve(num_output); |
| for (uint32_t i = 0; i < num_output; ++i) { |
| outputs.emplace_back(reinterpret_cast<NDArray*>(output_handles[i])); |
| } |
| |
| ograds.reserve(num_output); |
| for (uint32_t i = 0; i < num_output; ++i) { |
| if (ograd_handles != nullptr) { |
| ograds.emplace_back(reinterpret_cast<NDArray*>(ograd_handles[i])); |
| } else { |
| ograds.emplace_back(nullptr); |
| } |
| } |
| |
| variables.reserve(num_variables); |
| for (uint32_t i = 0; i < num_variables; ++i) { |
| variables.emplace_back(reinterpret_cast<NDArray*>(var_handles[i])); |
| } |
| |
| auto grads = |
| Imperative::Get()->Backward(outputs, ograds, variables, is_train, retain_graph, create_graph); |
| if (num_variables != 0) { |
| ret->ret_handles.clear(); |
| ret->out_types.clear(); |
| ret->ret_handles.reserve(grads.size()); |
| ret->out_types.reserve(grads.size()); |
| for (const auto& i : grads) { |
| ret->ret_handles.push_back(i); |
| ret->out_types.push_back(i->storage_type()); |
| } |
| *grad_handles = dmlc::BeginPtr(ret->ret_handles); |
| *grad_stypes = dmlc::BeginPtr(ret->out_types); |
| } |
| API_END(); |
| } |
| |
| int MXAutogradGetSymbol(NDArrayHandle handle, SymbolHandle* out) { |
| API_BEGIN(); |
| NDArray* head = reinterpret_cast<NDArray*>(handle); |
| auto sym = new nnvm::Symbol(head->get_autograd_symbol()); |
| *out = reinterpret_cast<SymbolHandle>(sym); |
| API_END(); |
| } |
| |
| int MXCachedOpRegisterOpHook(CachedOpHandle handle, |
| CachedOpMonitorCallback callback, |
| bool monitor_all) { |
| API_BEGIN(); |
| CachedOpMonitorCallback callback_temp = nullptr; |
| std::function<void(const char*, const char*, void*)> clbk; |
| if (callback) { |
| callback_temp = callback; |
| clbk = [callback_temp](const char* name, const char* opr_name, void* handle) { |
| callback_temp(name, opr_name, handle); |
| }; |
| } else { |
| clbk = nullptr; |
| } |
| CachedOpPtr op = *static_cast<CachedOpPtr*>(handle); |
| op->RegisterOpHook(clbk, monitor_all); |
| API_END(); |
| } |
| |
| int MXNDArrayIsDeferredCompute(int* curr) { |
| API_BEGIN(); |
| *curr = Imperative::Get()->is_deferred_compute(); |
| API_END(); |
| } |
| |
| int MXNDArraySetIsDeferredCompute(int deferred_compute, int* prev) { |
| API_BEGIN(); |
| *prev = Imperative::Get()->set_is_deferred_compute(static_cast<bool>(deferred_compute)); |
| API_END(); |
| } |
| |
| int MXNDArraySetDeferredComputeVariable(NDArrayHandle* arrays, SymbolHandle* variables, int num) { |
| API_BEGIN(); |
| Imperative::Get()->SetDeferredComputeVariable(arrays, variables, num); |
| API_END(); |
| } |
| |
| int MXNDArrayClearDeferredCompute(NDArrayHandle* arrays, int num) { |
| API_BEGIN(); |
| Imperative::Get()->DeferredComputeClear(arrays, num); |
| API_END(); |
| } |
| |
| int MXNDArrayGetDeferredComputeSymbol(NDArrayHandle* output_handles, |
| int num_outputs, |
| SymbolHandle* out) { |
| nnvm::Symbol* s = new nnvm::Symbol(); |
| API_BEGIN(); |
| std::vector<NDArray*> outputs; |
| outputs.reserve(num_outputs); |
| for (int i = 0; i < num_outputs; ++i) { |
| NDArray* array = reinterpret_cast<NDArray*>(output_handles[i]); |
| outputs.emplace_back(array); |
| } |
| // Obtain Symbol |
| *s = Imperative::Get()->GetDeferredComputeSymbol(outputs); |
| *out = s; |
| API_END_HANDLE_ERROR(delete s;); |
| } |