} // namespace
+namespace tensorflow {
+TFE_TensorHandle* ConvertToEagerTensor(PyObject* value, PyObject* dtype) {
+ int desired_dtype = -1;
+ if (dtype != Py_None) {
+ if (!PyIntToDataType(dtype, &desired_dtype)) {
+ PyErr_SetString(PyExc_TypeError,
+ tensorflow::strings::StrCat(
+ "Expecting a DataType value for dtype. Got ",
+ Py_TYPE(dtype)->tp_name)
+ .c_str());
+ return nullptr;
+ }
+ }
+ if (PyArray_Check(value)) {
+ int desired_np_dtype = -1;
+ if (desired_dtype >= 0) {
+ if (!tensorflow::TF_DataType_to_PyArray_TYPE(
+ static_cast<TF_DataType>(desired_dtype), &desired_np_dtype)
+ .ok()) {
+ PyErr_SetString(PyExc_TypeError,
+ tensorflow::strings::StrCat(
+ "Invalid dtype argument value ", desired_dtype)
+ .c_str());
+ return nullptr;
+ }
+ }
+ PyArrayObject* array = reinterpret_cast<PyArrayObject*>(value);
+ int current_np_dtype = PyArray_TYPE(array);
+ auto safe_value = tensorflow::make_safe(static_cast<PyObject*>(nullptr));
+ if ((desired_np_dtype >= 0 && desired_np_dtype != current_np_dtype) ||
+ !PyArray_ISCARRAY(array)) {
+ int new_dtype =
+ desired_np_dtype >= 0 ? desired_np_dtype : current_np_dtype;
+ safe_value = tensorflow::make_safe(
+ PyArray_FromAny(value, PyArray_DescrFromType(new_dtype), 0, 0,
+ NPY_ARRAY_CARRAY | NPY_ARRAY_FORCECAST, nullptr));
+ if (PyErr_Occurred()) return nullptr;
+ if (safe_value == nullptr) {
+ PyErr_SetString(PyExc_ValueError, "Error while casting a numpy value");
+ return nullptr;
+ }
+ value = safe_value.get();
+ }
+ return NumpyToTensorHandle(value);
+ } else {
+ tensorflow::Tensor t;
+ // TODO(josh11b): Have PySeqToTensor set python errors instead of
+ // returning Status.
+ auto cppstatus = tensorflow::PySeqToTensor(value, dtype, &t);
+ if (!cppstatus.ok()) {
+ PyErr_SetString(PyExc_ValueError, cppstatus.error_message().c_str());
+ return nullptr;
+ }
+ return TFE_NewTensorHandle(t);
+ }
+}
+} // namespace tensorflow
+
extern "C" {
static const int kMaxEagerTensorParentSize = 64;
return -1;
}
}
- tensorflow::Safe_TFE_TensorHandlePtr handle =
- tensorflow::make_safe(static_cast<TFE_TensorHandle*>(nullptr));
PyErr_Clear();
- if (PyArray_Check(value)) {
- int desired_np_dtype = -1;
- if (desired_dtype >= 0) {
- if (!tensorflow::TF_DataType_to_PyArray_TYPE(
- static_cast<TF_DataType>(desired_dtype), &desired_np_dtype)
- .ok()) {
- PyErr_SetString(PyExc_TypeError,
- tensorflow::strings::StrCat(
- "Invalid dtype argument value ", desired_dtype)
- .c_str());
- return -1;
- }
- }
- PyArrayObject* array = reinterpret_cast<PyArrayObject*>(value);
- int current_np_dtype = PyArray_TYPE(array);
- auto safe_value = tensorflow::make_safe(static_cast<PyObject*>(nullptr));
- if ((desired_np_dtype >= 0 && desired_np_dtype != current_np_dtype) ||
- !PyArray_ISCARRAY(array)) {
- int new_dtype =
- desired_np_dtype >= 0 ? desired_np_dtype : current_np_dtype;
- safe_value = tensorflow::make_safe(
- PyArray_FromAny(value, PyArray_DescrFromType(new_dtype), 0, 0,
- NPY_ARRAY_CARRAY | NPY_ARRAY_FORCECAST, nullptr));
- if (PyErr_Occurred()) return -1;
- if (safe_value == nullptr) {
- PyErr_SetString(PyExc_ValueError, "Error while casting a numpy value");
- return -1;
- }
- value = safe_value.get();
- }
- handle = tensorflow::make_safe(NumpyToTensorHandle(value));
- } else {
- tensorflow::Tensor t;
- // TODO(josh11b): Have PySeqToTensor set python errors instead of
- // returning Status.
- auto cppstatus = tensorflow::PySeqToTensor(value, dtype, &t);
- if (!cppstatus.ok()) {
- PyErr_SetString(PyExc_ValueError, cppstatus.error_message().c_str());
- return -1;
- }
- handle = tensorflow::make_safe(TFE_NewTensorHandle(t));
- }
- if (PyErr_Occurred()) return -1;
- if (handle == nullptr) {
- PyErr_SetString(PyExc_ValueError, "Error while creating an EagerTensor");
- return -1;
- }
+ tensorflow::Safe_TFE_TensorHandlePtr handle =
+ tensorflow::make_safe(static_cast<TFE_TensorHandle*>(
+ tensorflow::ConvertToEagerTensor(value, dtype)));
+ if (handle == nullptr) return -1;
TF_DataType handle_dtype = TFE_TensorHandleDataType(handle.get());
if (desired_dtype >= 0 && desired_dtype != handle_dtype) {
handle = tensorflow::make_safe(
return reinterpret_cast<PyObject*>(EagerTensorType);
}
-PyObject* TFE_Py_TensorShapeSlice(PyObject* tensor_list, int slice_dim) {
- if (!PyList_Check(tensor_list)) {
+PyObject* TFE_Py_TensorShapeSlice(PyObject* tensors, int slice_dim) {
+ if (!PyList_Check(tensors) && !PyTuple_Check(tensors)) {
PyErr_SetString(PyExc_TypeError,
tensorflow::strings::StrCat(
- "tensor_list argument must be a list. Got \"",
- Py_TYPE(tensor_list)->tp_name, "\"")
+ "tensors argument must be a list or a tuple. Got \"",
+ Py_TYPE(tensors)->tp_name, "\"")
.c_str());
return nullptr;
}
return nullptr;
}
- Py_ssize_t num_tensors = PyList_Size(tensor_list);
+ Py_ssize_t num_tensors = PySequence_Fast_GET_SIZE(tensors);
int64_t num_tensors_int = static_cast<int64_t>(num_tensors);
auto tensor = tensorflow::make_safe(TF_AllocateTensor(
TF_INT32, &num_tensors_int, /*num_dims=*/1, /*len=*/4 * num_tensors_int));
int32_t* data = reinterpret_cast<int32_t*>(TF_TensorData(tensor.get()));
auto status = tensorflow::make_safe(TF_NewStatus());
for (Py_ssize_t i = 0; i < num_tensors; ++i) {
- PyObject* tensor_obj = PyList_GET_ITEM(tensor_list, i);
+ PyObject* tensor_obj = PySequence_Fast_GET_ITEM(tensors, i);
if (!EagerTensor_CheckExact(tensor_obj)) {
PyErr_SetString(PyExc_TypeError,
tensorflow::strings::StrCat(
namespace {
+struct InputInfo {
+ InputInfo(PyObject* item, bool is_list) : item(item), is_list(is_list) {}
+ PyObject* item = nullptr;
+
+ bool is_list = false;
+};
+
struct FastPathOpExecInfo {
TFE_Context* ctx;
const char* device_name;
// The op type name of the main op being executed.
PyObject* op_name;
PyObject* callbacks;
+
+ // DTypes can come from another input that has the same attr. So build that
+ // map.
+ tensorflow::gtl::FlatMap<string, tensorflow::gtl::InlinedVector<InputInfo, 4>>
+ attr_to_inputs;
+ tensorflow::gtl::FlatMap<string, tensorflow::DataType> cached_dtypes;
};
#define PARSE_VALUE(fn_name, type, check_fn, parse_fn) \
PARSE_VALUE(ParseInt64Value, int64_t, PyLong_Check, PyLong_AsLong)
#else
PARSE_VALUE(ParseIntValue, int, PyInt_Check, PyInt_AsLong)
-PARSE_VALUE(ParseInt64Value, int64_t, PyInt_Check, PyInt_AsLong)
-PARSE_VALUE(ParseInt64LongValue, int64_t, PyLong_Check, PyLong_AsLong)
#endif
PARSE_VALUE(ParseFloatValue, float, PyFloat_Check, PyFloat_AsDouble)
#undef PARSE_VALUE
+#if PY_MAJOR_VERSION < 3
+bool ParseInt64Value(const string& key, PyObject* py_value, TF_Status* status,
+ int64_t* value) {
+ if (PyInt_Check(py_value)) {
+ *value = static_cast<int64_t>(PyInt_AsLong(py_value));
+ return true;
+ } else if (PyLong_Check(py_value)) {
+ *value = static_cast<int64_t>(PyLong_AsLong(py_value));
+ return true;
+ }
+ TF_SetStatus(
+ status, TF_INVALID_ARGUMENT,
+ tensorflow::strings::StrCat("Expecting int or long value for attr ", key,
+ ", got ", py_value->ob_type->tp_name)
+ .c_str());
+ return false;
+}
+#endif
+
Py_ssize_t TensorShapeNumDims(PyObject* value) {
const auto size = PySequence_Size(value);
if (size == -1) {
std::unique_ptr<int64_t[]> buffer(new int64_t[total_dims]);
// Copy the input dims into the buffer and set dims to point to
// the start of each list's dims.
- std::unique_ptr<const int64_t* []> dims(new const int64_t*[num_values]);
+ std::unique_ptr<const int64_t*[]> dims(new const int64_t*[num_values]);
std::unique_ptr<int[]> num_dims(new int[num_values]);
int64_t* offset = buffer.get();
for (int i = 0; i < num_values; ++i) {
TF_Status* status) {
if (type == TF_ATTR_STRING) {
int num_values = attr.default_value().list().s_size();
- std::unique_ptr<const char* []> values(new const char*[num_values]);
+ std::unique_ptr<const char*[]> values(new const char*[num_values]);
(*attr_list_sizes)[key] = num_values;
for (int i = 0; i < num_values; i++) {
values[i] = attr.default_value().list().s(i).data();
std::unique_ptr<int64_t[]> buffer(new int64_t[total_dims]);
// Copy the input dims into the buffer and set dims to point to
// the start of each list's dims.
- std::unique_ptr<const int64_t* []> dims(new const int64_t*[num_values]);
+ std::unique_ptr<const int64_t*[]> dims(new const int64_t*[num_values]);
std::unique_ptr<int[]> num_dims(new int[num_values]);
int64_t* offset = buffer.get();
for (int i = 0; i < num_values; ++i) {
} else if (type == TF_ATTR_FUNC) {
int num_values = attr.default_value().list().func_size();
(*attr_list_sizes)[key] = num_values;
- std::unique_ptr<const TFE_Op* []> funcs(new const TFE_Op*[num_values]);
+ std::unique_ptr<const TFE_Op*[]> funcs(new const TFE_Op*[num_values]);
for (int i = 0; i < num_values; i++) {
funcs[i] = GetFunc(ctx, attr.default_value().list().func(i), status);
}
#endif
}
+PyObject* GetPythonObjectFromInt(int num) {
+#if PY_MAJOR_VERSION >= 3
+ return PyLong_FromLong(num);
+#else
+ return PyInt_FromLong(num);
+#endif
+}
+
bool CheckResourceVariable(PyObject* item) {
return PyObject_TypeCheck(item, resource_variable_type);
}
+bool IsNumberType(PyObject* item) {
+#if PY_MAJOR_VERSION >= 3
+ return PyFloat_Check(item) || PyLong_Check(item);
+#else
+ return PyFloat_Check(item) || PyInt_Check(item) || PyLong_Check(item);
+#endif
+}
+
+bool CheckOneInput(PyObject* item) {
+ if (EagerTensor_CheckExact(item) || CheckResourceVariable(item) ||
+ PyArray_Check(item) || IsNumberType(item)) {
+ return true;
+ }
+
+ // Sequences are not properly handled. Sequences with purely python numeric
+ // types work, but sequences with mixes of EagerTensors and python numeric
+ // types don't work.
+ // TODO(nareshmodi): fix
+ return false;
+}
+
bool CheckInputsOk(PyObject* seq, int start_index,
const tensorflow::OpDef& op_def) {
for (int i = 0; i < op_def.input_arg_size(); i++) {
}
for (Py_ssize_t j = 0; j < PySequence_Fast_GET_SIZE(item); j++) {
PyObject* inner_item = PySequence_Fast_GET_ITEM(item, j);
- if (!EagerTensor_CheckExact(inner_item) &&
- !CheckResourceVariable(inner_item)) {
+ if (!CheckOneInput(inner_item)) {
VLOG(1)
<< "Falling back to slow path for Op \"" << op_def.name()
<< "\", Input \"" << op_def.input_arg(i).name() << "\", Index "
return false;
}
}
- } else if (!EagerTensor_CheckExact(item) && !CheckResourceVariable(item)) {
+ } else if (!CheckOneInput(item)) {
VLOG(1)
<< "Falling back to slow path for Op \"" << op_def.name()
<< "\", Input \"" << op_def.input_arg(i).name()
return true;
}
+PyObject* MaybeGetDType(PyObject* item) {
+ if (EagerTensor_CheckExact(item)) {
+ tensorflow::Safe_PyObjectPtr py_dtype(
+ PyObject_GetAttrString(item, "dtype"));
+ return PyObject_GetAttrString(py_dtype.get(), "_type_enum");
+ }
+
+ if (CheckResourceVariable(item)) {
+ tensorflow::Safe_PyObjectPtr py_dtype(
+ PyObject_GetAttrString(item, "_dtype"));
+ return PyObject_GetAttrString(py_dtype.get(), "_type_enum");
+ }
+
+ return nullptr;
+}
+
+PyObject* MaybeGetDTypeForAttr(const string& attr,
+ FastPathOpExecInfo* op_exec_info) {
+ auto cached_it = op_exec_info->cached_dtypes.find(attr);
+ if (cached_it != op_exec_info->cached_dtypes.end()) {
+ return GetPythonObjectFromInt(cached_it->second);
+ }
+
+ auto it = op_exec_info->attr_to_inputs.find(attr);
+ if (it == op_exec_info->attr_to_inputs.end()) {
+ // No other inputs - this should never happen.
+ Py_RETURN_NONE;
+ }
+
+ for (const auto& input_info : it->second) {
+ if (input_info.is_list) {
+ for (int i = 0; i < PySequence_Fast_GET_SIZE(input_info.item); i++) {
+ auto* dtype =
+ MaybeGetDType(PySequence_Fast_GET_ITEM(input_info.item, i));
+ if (dtype != nullptr) return dtype;
+ }
+ } else {
+ auto* dtype = MaybeGetDType(input_info.item);
+ if (dtype != nullptr) return dtype;
+ }
+ }
+
+ Py_RETURN_NONE;
+}
+
bool OpDoesntRequireOutput(const string& op_name) {
static tensorflow::gtl::FlatSet<string>* ops_that_dont_require_outputs =
new tensorflow::gtl::FlatSet<string>({
// i) input is an EagerTensor
// ii) input is a ResourceVariable - in this case, the is_variable param is set
// to true.
-bool ConvertToTensor(const FastPathOpExecInfo& op_exec_info, PyObject* input,
- tensorflow::Safe_PyObjectPtr* output_handle,
- TF_Status* status) {
- if (CheckResourceVariable(input)) {
+//
+// NOTE: dtype_hint_getter must *always* return a PyObject that can be
+// decref'd. So if no hint is found, Py_RETURN_NONE (which correctly
+// increfs Py_None).
+bool ConvertToTensor(
+ const FastPathOpExecInfo& op_exec_info, PyObject* input,
+ tensorflow::Safe_PyObjectPtr* output_handle,
+ // This gets a hint for this particular input.
+ const std::function<PyObject*()>& dtype_hint_getter,
+ // This sets the dtype after conversion is complete.
+ const std::function<void(const TF_DataType& dtype)>& dtype_setter,
+ TF_Status* status) {
+ if (EagerTensor_CheckExact(input)) {
+ Py_INCREF(input);
+ output_handle->reset(input);
+ return true;
+ } else if (CheckResourceVariable(input)) {
return ReadVariableOp(op_exec_info, input, output_handle, status);
}
- Py_INCREF(input);
- output_handle->reset(input);
+ // The hint comes from a supposedly similarly typed tensor.
+ tensorflow::Safe_PyObjectPtr dtype_hint(dtype_hint_getter());
+ if (PyErr_Occurred()) {
+ return false;
+ }
+
+ auto* handle = tensorflow::ConvertToEagerTensor(input, dtype_hint.get());
+ if (handle == nullptr) {
+ status->status = tensorflow::errors::InvalidArgument(
+ "Unable to convert value to tensor");
+ return false;
+ }
+
+ output_handle->reset(EagerTensorFromHandle(handle));
+
+ auto dtype_actual = TFE_TensorHandleDataType(handle);
+ dtype_setter(dtype_actual);
return true;
}
// Adds input and type attr to the op, and to the list of flattened
// inputs/attrs.
-bool AddInputToOp(const FastPathOpExecInfo& op_exec_info, PyObject* input,
- const tensorflow::OpDef::ArgDef* input_arg,
+bool AddInputToOp(FastPathOpExecInfo* op_exec_info, PyObject* input,
+ const bool add_type_attr,
+ const tensorflow::OpDef::ArgDef& input_arg,
std::vector<tensorflow::Safe_PyObjectPtr>* flattened_attrs,
std::vector<tensorflow::Safe_PyObjectPtr>* flattened_inputs,
TFE_Op* op, TF_Status* status) {
// out of scope in this function.
tensorflow::Safe_PyObjectPtr py_eager_tensor = nullptr;
- if (!ConvertToTensor(op_exec_info, input, &py_eager_tensor, status)) {
+ if (!ConvertToTensor(
+ *op_exec_info, input, &py_eager_tensor,
+ [&]() {
+ if (input_arg.type() != tensorflow::DataType::DT_INVALID) {
+ return GetPythonObjectFromInt(input_arg.type());
+ }
+ return MaybeGetDTypeForAttr(input_arg.type_attr(), op_exec_info);
+ },
+ [&](const TF_DataType dtype) {
+ op_exec_info->cached_dtypes[input_arg.type_attr()] =
+ static_cast<tensorflow::DataType>(dtype);
+ },
+ status)) {
return false;
}
TFE_TensorHandle* input_handle = EagerTensor_Handle(py_eager_tensor.get());
- if (input_arg != nullptr && !input_arg->type_attr().empty()) {
+ if (add_type_attr && !input_arg.type_attr().empty()) {
auto dtype = TFE_TensorHandleDataType(input_handle);
- TFE_OpSetAttrType(op, input_arg->type_attr().data(), dtype);
+ TFE_OpSetAttrType(op, input_arg.type_attr().data(), dtype);
if (flattened_attrs != nullptr) {
flattened_attrs->emplace_back(
- GetPythonObjectFromString(input_arg->type_attr().data()));
+ GetPythonObjectFromString(input_arg.type_attr().data()));
flattened_attrs->emplace_back(PyLong_FromLong(dtype));
}
}
return nullptr;
}
+ // This can be cached somewhere.
+ // Store a list of InputIndex -> List of corresponding inputs.
+ for (int i = 0; i < op_def->input_arg_size(); i++) {
+ if (!op_def->input_arg(i).type_attr().empty()) {
+ auto it =
+ op_exec_info.attr_to_inputs.find(op_def->input_arg(i).type_attr());
+ if (it == op_exec_info.attr_to_inputs.end()) {
+ it = op_exec_info.attr_to_inputs
+ .insert({op_def->input_arg(i).type_attr(), {}})
+ .first;
+ }
+ it->second.emplace_back(
+ PyTuple_GET_ITEM(args, kFastPathExecuteInputStartIndex + i),
+ !op_def->input_arg(i).number_attr().empty());
+ }
+ }
+
TF_Status* status = TF_NewStatus();
TFE_Op* op = TFE_NewOp(op_exec_info.ctx, op_def->name().c_str(), status);
auto cleaner = tensorflow::gtl::MakeCleanup([status, op] {
if (len > 0) {
// First item adds the type attr.
- if (!AddInputToOp(op_exec_info, PySequence_Fast_GET_ITEM(input, 0),
- &input_arg, flattened_attrs.get(),
+ if (!AddInputToOp(&op_exec_info, PySequence_Fast_GET_ITEM(input, 0),
+ true, input_arg, flattened_attrs.get(),
flattened_inputs.get(), op, status)) {
return nullptr;
}
for (Py_ssize_t j = 1; j < len; j++) {
// Since the list is homogeneous, we don't need to re-add the attr.
- if (!AddInputToOp(op_exec_info, PySequence_Fast_GET_ITEM(input, j),
- nullptr /* input_arg */,
- nullptr /* flattened_attrs */,
+ if (!AddInputToOp(&op_exec_info, PySequence_Fast_GET_ITEM(input, j),
+ false, input_arg, nullptr /* flattened_attrs */,
flattened_inputs.get(), op, status)) {
return nullptr;
}
PyObject* py_input = PySequence_Fast_GET_ITEM(input, j);
tensorflow::Safe_PyObjectPtr py_eager_tensor;
if (!ConvertToTensor(op_exec_info, py_input, &py_eager_tensor,
- status)) {
+ []() { Py_RETURN_NONE; },
+ [](const TF_DataType& dtype) {}, status)) {
return nullptr;
}
attr_list_sizes[attr_name] = len;
} else {
// The item is a single item.
- if (!AddInputToOp(op_exec_info, input, &input_arg, flattened_attrs.get(),
- flattened_inputs.get(), op, status)) {
+ if (!AddInputToOp(&op_exec_info, input, true, input_arg,
+ flattened_attrs.get(), flattened_inputs.get(), op,
+ status)) {
return nullptr;
}
}