From 76ce1a3b205d87cd3328c4efac38f61182c541df Mon Sep 17 00:00:00 2001 From: wyg1997 Date: Thu, 31 Mar 2022 13:19:52 +0800 Subject: [PATCH 1/6] feat(functional): support numpy scalar parameters --- oneflow/api/python/functional/python_arg.cpp | 11 ++++++++--- oneflow/api/python/utils/tensor_utils.h | 1 - oneflow/extension/python/numpy.cpp | 8 ++++++++ oneflow/extension/python/numpy_internal.h | 4 ++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/oneflow/api/python/functional/python_arg.cpp b/oneflow/api/python/functional/python_arg.cpp index dadeab426ef..80bcb85f561 100644 --- a/oneflow/api/python/functional/python_arg.cpp +++ b/oneflow/api/python/functional/python_arg.cpp @@ -17,6 +17,7 @@ limitations under the License. #include "oneflow/api/python/functional/python_arg.h" #include "oneflow/api/python/functional/common.h" #include "oneflow/api/python/functional/indexing.h" +#include "oneflow/extension/python/numpy.h" #include "oneflow/core/common/scalar.h" #include "oneflow/core/framework/dtype.h" #include "oneflow/core/framework/device.h" @@ -198,21 +199,25 @@ Maybe PythonArg::TypeCheck(ValueType type) const { case kUINT32: case kINT64: case kUINT64: - case kBOOL: return PyLong_Check(object_); + case kBOOL: return PyLong_Check(object_) || numpy::IsNumpyLongScalar(object_); case kINT32_LIST: case kUINT32_LIST: case kINT64_LIST: case kUINT64_LIST: case kBOOL_LIST: return PyLongSequenceCheck(object_) || (size_ > 0 && PyLong_Check(object_)); case kFLOAT: - case kDOUBLE: return PyFloat_Check(object_) || PyLong_Check(object_); + case kDOUBLE: + return PyFloat_Check(object_) || PyLong_Check(object_) || numpy::IsNumpyFloatScalar(object_) + || numpy::IsNumpyLongScalar(object_); case kFLOAT_LIST: case kDOUBLE_LIST: return PyFloatSquenceCheck(object_) || (size_ > 0 && (PyFloat_Check(object_) || PyLong_Check(object_))); case kSTRING: return PyStringCheck(object_); case kSTRING_LIST: return PyStringSequenceCheck(object_); - case kSCALAR: return PyScalarCheck(object_); + case kSCALAR: + return PyScalarCheck(object_) || numpy::IsNumpyLongScalar(object_) + || numpy::IsNumpyFloatScalar(object_); case kTENSOR: case kTENSOR_REF: return PyTensorCheck(object_); case kTENSOR_TUPLE: return PyTensorTupleCheck(object_) || PyTensorSequenceCheck(object_); diff --git a/oneflow/api/python/utils/tensor_utils.h b/oneflow/api/python/utils/tensor_utils.h index af41cedcb3f..d2c9411cfef 100644 --- a/oneflow/api/python/utils/tensor_utils.h +++ b/oneflow/api/python/utils/tensor_utils.h @@ -31,7 +31,6 @@ limitations under the License. #include "oneflow/core/register/ofblob.h" #include "oneflow/core/common/blocking_then_busy.h" #include "oneflow/core/vm/virtual_machine.h" -#include "oneflow/extension/python/numpy.h" #include "oneflow/core/common/foreign_lock_helper.h" namespace py = pybind11; diff --git a/oneflow/extension/python/numpy.cpp b/oneflow/extension/python/numpy.cpp index 99a313cad32..831da5d5b2b 100644 --- a/oneflow/extension/python/numpy.cpp +++ b/oneflow/extension/python/numpy.cpp @@ -90,6 +90,14 @@ std::vector OFStrideToNumpyStride(const StrideVector& fixed_vec, const D return result; } +bool IsNumpyLongScalar(PyObject* obj) { + return PyArray_CheckScalar(obj) && PyDataType_ISINTEGER(PyArray_DescrFromScalar(obj)); +} + +bool IsNumpyFloatScalar(PyObject* obj) { + return PyArray_CheckScalar(obj) && PyDataType_ISFLOAT(PyArray_DescrFromScalar(obj)); +} + // Executing any numpy c api before _import_array() results in segfault // NOTE: this InitNumpyCAPI() works because of `PY_ARRAY_UNIQUE_SYMBOL` // defined in numpy_internal.h diff --git a/oneflow/extension/python/numpy_internal.h b/oneflow/extension/python/numpy_internal.h index 7a0375c9875..ccf1f363fca 100644 --- a/oneflow/extension/python/numpy_internal.h +++ b/oneflow/extension/python/numpy_internal.h @@ -62,6 +62,10 @@ std::vector OFShapeToNumpyShape(const DimVector& fixed_vec); std::vector OFStrideToNumpyStride(const StrideVector& fixed_vec, const DataType data_type); +bool IsNumpyLongScalar(PyObject* obj); + +bool IsNumpyFloatScalar(PyObject* obj); + Maybe InitNumpyCAPI(); } // namespace numpy From d05101d22f40161bc44b2e86b6109102337449bc Mon Sep 17 00:00:00 2001 From: wyg1997 Date: Thu, 31 Mar 2022 14:35:59 +0800 Subject: [PATCH 2/6] rename inferface --- oneflow/api/python/functional/python_arg.cpp | 10 +++++----- oneflow/extension/python/numpy.cpp | 4 ++-- oneflow/extension/python/numpy_internal.h | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/oneflow/api/python/functional/python_arg.cpp b/oneflow/api/python/functional/python_arg.cpp index 80bcb85f561..ac48f1513d4 100644 --- a/oneflow/api/python/functional/python_arg.cpp +++ b/oneflow/api/python/functional/python_arg.cpp @@ -199,7 +199,7 @@ Maybe PythonArg::TypeCheck(ValueType type) const { case kUINT32: case kINT64: case kUINT64: - case kBOOL: return PyLong_Check(object_) || numpy::IsNumpyLongScalar(object_); + case kBOOL: return PyLong_Check(object_) || numpy::PyArrayCheckLongScalar(object_); case kINT32_LIST: case kUINT32_LIST: case kINT64_LIST: @@ -207,8 +207,8 @@ Maybe PythonArg::TypeCheck(ValueType type) const { case kBOOL_LIST: return PyLongSequenceCheck(object_) || (size_ > 0 && PyLong_Check(object_)); case kFLOAT: case kDOUBLE: - return PyFloat_Check(object_) || PyLong_Check(object_) || numpy::IsNumpyFloatScalar(object_) - || numpy::IsNumpyLongScalar(object_); + return PyFloat_Check(object_) || PyLong_Check(object_) + || numpy::PyArrayCheckFloatScalar(object_) || numpy::PyArrayCheckLongScalar(object_); case kFLOAT_LIST: case kDOUBLE_LIST: return PyFloatSquenceCheck(object_) @@ -216,8 +216,8 @@ Maybe PythonArg::TypeCheck(ValueType type) const { case kSTRING: return PyStringCheck(object_); case kSTRING_LIST: return PyStringSequenceCheck(object_); case kSCALAR: - return PyScalarCheck(object_) || numpy::IsNumpyLongScalar(object_) - || numpy::IsNumpyFloatScalar(object_); + return PyScalarCheck(object_) || numpy::PyArrayCheckLongScalar(object_) + || numpy::PyArrayCheckFloatScalar(object_); case kTENSOR: case kTENSOR_REF: return PyTensorCheck(object_); case kTENSOR_TUPLE: return PyTensorTupleCheck(object_) || PyTensorSequenceCheck(object_); diff --git a/oneflow/extension/python/numpy.cpp b/oneflow/extension/python/numpy.cpp index 831da5d5b2b..2f0d762c04e 100644 --- a/oneflow/extension/python/numpy.cpp +++ b/oneflow/extension/python/numpy.cpp @@ -90,11 +90,11 @@ std::vector OFStrideToNumpyStride(const StrideVector& fixed_vec, const D return result; } -bool IsNumpyLongScalar(PyObject* obj) { +bool PyArrayCheckLongScalar(PyObject* obj) { return PyArray_CheckScalar(obj) && PyDataType_ISINTEGER(PyArray_DescrFromScalar(obj)); } -bool IsNumpyFloatScalar(PyObject* obj) { +bool PyArrayCheckFloatScalar(PyObject* obj) { return PyArray_CheckScalar(obj) && PyDataType_ISFLOAT(PyArray_DescrFromScalar(obj)); } diff --git a/oneflow/extension/python/numpy_internal.h b/oneflow/extension/python/numpy_internal.h index ccf1f363fca..31a9430a288 100644 --- a/oneflow/extension/python/numpy_internal.h +++ b/oneflow/extension/python/numpy_internal.h @@ -62,9 +62,9 @@ std::vector OFShapeToNumpyShape(const DimVector& fixed_vec); std::vector OFStrideToNumpyStride(const StrideVector& fixed_vec, const DataType data_type); -bool IsNumpyLongScalar(PyObject* obj); +bool PyArrayCheckLongScalar(PyObject* obj); -bool IsNumpyFloatScalar(PyObject* obj); +bool PyArrayCheckFloatScalar(PyObject* obj); Maybe InitNumpyCAPI(); From ce6dd6ae66c7dd1529921f69d34b4b86bcb45be7 Mon Sep 17 00:00:00 2001 From: wyg1997 Date: Thu, 31 Mar 2022 15:22:53 +0800 Subject: [PATCH 3/6] feat(*): TensorIndex support numpy scalar --- oneflow/api/python/functional/common.cpp | 6 ++++-- oneflow/api/python/functional/indexing.cpp | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/oneflow/api/python/functional/common.cpp b/oneflow/api/python/functional/common.cpp index b96fd93a4d0..fc3785e95c7 100644 --- a/oneflow/api/python/functional/common.cpp +++ b/oneflow/api/python/functional/common.cpp @@ -19,6 +19,7 @@ limitations under the License. #include #include "oneflow/api/python/functional/indexing.h" +#include "oneflow/extension/python/numpy.h" #include "oneflow/core/common/just.h" #include "oneflow/core/common/scalar.h" #include "oneflow/core/framework/dtype.h" @@ -195,8 +196,9 @@ Maybe>> PyUnpackSbpParallelSequence(PyObject* ob // Tensor index bool PyTensorIndexCheck(PyObject* obj) { - return PySlice_Check(obj) || PyLong_Check(obj) || obj == Py_Ellipsis || obj == Py_None - || PyTensorCheck(obj) || PySequence_Check(obj) || PyUnicode_Check(obj); + return PySlice_Check(obj) || PyLong_Check(obj) || numpy::PyArrayCheckLongScalar(obj) + || obj == Py_Ellipsis || obj == Py_None || PyTensorCheck(obj) || PySequence_Check(obj) + || PyUnicode_Check(obj); } Maybe PyUnpackTensorIndex(PyObject* obj) { auto tensor_index = std::make_shared(); diff --git a/oneflow/api/python/functional/indexing.cpp b/oneflow/api/python/functional/indexing.cpp index 467f7a526e7..52d0437e9ba 100644 --- a/oneflow/api/python/functional/indexing.cpp +++ b/oneflow/api/python/functional/indexing.cpp @@ -193,6 +193,8 @@ Maybe UnpackIndexItem(PyObject* object) { return std::make_shared(start, end, step); } else if (PyLong_Check(object) && object != Py_False && object != Py_True) { return std::make_shared(static_cast(PyLong_AsLongLong(object))); + } else if (numpy::PyArrayCheckLongScalar(object)) { + return std::make_shared(static_cast(PyLong_AsLongLong(object))); } else if (object == Py_False || object == Py_True) { return std::make_shared(object == Py_True); } else if (object == Py_None) { From 551e43423a2ba7fc387c44d2d867739f3d4706fc Mon Sep 17 00:00:00 2001 From: wyg1997 Date: Thu, 31 Mar 2022 17:45:27 +0800 Subject: [PATCH 4/6] feat(TensorIndex): support advance indexing --- oneflow/api/python/functional/common.cpp | 13 +++++++------ oneflow/api/python/functional/indexing.cpp | 10 +++++++--- oneflow/extension/python/numpy.cpp | 4 ++++ oneflow/extension/python/numpy_internal.h | 2 ++ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/oneflow/api/python/functional/common.cpp b/oneflow/api/python/functional/common.cpp index fc3785e95c7..0d5465ccf82 100644 --- a/oneflow/api/python/functional/common.cpp +++ b/oneflow/api/python/functional/common.cpp @@ -203,12 +203,13 @@ bool PyTensorIndexCheck(PyObject* obj) { Maybe PyUnpackTensorIndex(PyObject* obj) { auto tensor_index = std::make_shared(); // Obvious single-entry cases. - if (PySlice_Check(obj) // NOLINT - || PyLong_Check(obj) // NOLINT - || obj == Py_Ellipsis // NOLINT - || obj == Py_None // NOLINT - || PyTensorCheck(obj) // NOLINT - || !PySequence_Check(obj) // NOLINT + if (PySlice_Check(obj) // NOLINT + || PyLong_Check(obj) // NOLINT + || numpy::PyArrayCheckLongScalar(obj) // NOLINT + || obj == Py_Ellipsis // NOLINT + || obj == Py_None // NOLINT + || PyTensorCheck(obj) // NOLINT + || !PySequence_Check(obj) // NOLINT || PyUnicode_Check(obj)) { tensor_index->emplace_back(*JUST(detail::UnpackIndexItem(obj))); return tensor_index; diff --git a/oneflow/api/python/functional/indexing.cpp b/oneflow/api/python/functional/indexing.cpp index 52d0437e9ba..e7b5bda166e 100644 --- a/oneflow/api/python/functional/indexing.cpp +++ b/oneflow/api/python/functional/indexing.cpp @@ -65,6 +65,8 @@ Maybe InferScalarType(PyObject* object) { return DataType::kInt64; } else if (PyArray_Check(object)) { return numpy::GetOFDataTypeFromNpArray(reinterpret_cast(object)); + } else if (PyArray_CheckScalar(object)) { + return numpy::NumpyTypeToOFDataType(PyArray_DescrFromScalar(object)->type_num); } else if (PySequence_Check(object)) { int64_t length = PySequence_Length(object); CHECK_GT_OR_RETURN(length, 0) << "Index should not be empty."; @@ -86,13 +88,15 @@ Maybe InferScalarType(PyObject* object) { Maybe ParseScalar(PyObject* object, char* data, const DataType& dtype) { if (dtype == DataType::kInt64) { - CHECK_OR_RETURN(PyLong_Check(object)) << "Expected a long value."; + CHECK_OR_RETURN(PyLong_Check(object) || numpy::PyArrayCheckLongScalar(object)) + << "Expected a long value."; *(reinterpret_cast(data)) = PyLong_AsLongLong(object); return Maybe::Ok(); } else if (dtype == DataType::kUInt8 || dtype == DataType::kBool) { - CHECK_OR_RETURN(PyBool_Check(object) || PyLong_Check(object)) + CHECK_OR_RETURN(PyBool_Check(object) || PyLong_Check(object) + || numpy::PyArrayCheckLongScalar(object)) << "Expected a boolean or long value."; - if (PyBool_Check(object)) { + if (PyBool_Check(object) || numpy::PyArrayCheckBoolScalar(object)) { *(reinterpret_cast(data)) = (object == Py_True); } else { int64_t value = PyLong_AsLongLong(object); diff --git a/oneflow/extension/python/numpy.cpp b/oneflow/extension/python/numpy.cpp index 2f0d762c04e..2efee8e3fb7 100644 --- a/oneflow/extension/python/numpy.cpp +++ b/oneflow/extension/python/numpy.cpp @@ -98,6 +98,10 @@ bool PyArrayCheckFloatScalar(PyObject* obj) { return PyArray_CheckScalar(obj) && PyDataType_ISFLOAT(PyArray_DescrFromScalar(obj)); } +bool PyArrayCheckBoolScalar(PyObject* obj) { + return PyArray_CheckScalar(obj) && PyDataType_ISBOOL(PyArray_DescrFromScalar(obj)); +} + // Executing any numpy c api before _import_array() results in segfault // NOTE: this InitNumpyCAPI() works because of `PY_ARRAY_UNIQUE_SYMBOL` // defined in numpy_internal.h diff --git a/oneflow/extension/python/numpy_internal.h b/oneflow/extension/python/numpy_internal.h index 31a9430a288..22e4a6694ce 100644 --- a/oneflow/extension/python/numpy_internal.h +++ b/oneflow/extension/python/numpy_internal.h @@ -66,6 +66,8 @@ bool PyArrayCheckLongScalar(PyObject* obj); bool PyArrayCheckFloatScalar(PyObject* obj); +bool PyArrayCheckBoolScalar(PyObject* obj); + Maybe InitNumpyCAPI(); } // namespace numpy From e3e44f3545b5b1a1600adc33dbe4b6119fb21f04 Mon Sep 17 00:00:00 2001 From: Wang Yi <53533850+marigoold@users.noreply.github.com> Date: Sat, 2 Apr 2022 14:32:20 +0800 Subject: [PATCH 5/6] add unittest and int32 support for branch feat-param_support_np_scalar (#7939) * add unittest * refactor unittest * add todo for int16 advanced indexing * add int32 supporting for advance indexing --- oneflow/api/python/functional/indexing.cpp | 5 ++ .../test/tensor/test_tensor_indexing.py | 83 +++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/oneflow/api/python/functional/indexing.cpp b/oneflow/api/python/functional/indexing.cpp index e7b5bda166e..d92cb23f86d 100644 --- a/oneflow/api/python/functional/indexing.cpp +++ b/oneflow/api/python/functional/indexing.cpp @@ -92,6 +92,11 @@ Maybe ParseScalar(PyObject* object, char* data, const DataType& dtype) { << "Expected a long value."; *(reinterpret_cast(data)) = PyLong_AsLongLong(object); return Maybe::Ok(); + } else if (dtype == DataType::kInt32) { + CHECK_OR_RETURN(PyLong_Check(object) || numpy::PyArrayCheckLongScalar(object)) + << "Expected a long value."; + *(reinterpret_cast(data)) = PyLong_AsLongLong(object); + return Maybe::Ok(); } else if (dtype == DataType::kUInt8 || dtype == DataType::kBool) { CHECK_OR_RETURN(PyBool_Check(object) || PyLong_Check(object) || numpy::PyArrayCheckLongScalar(object)) diff --git a/python/oneflow/test/tensor/test_tensor_indexing.py b/python/oneflow/test/tensor/test_tensor_indexing.py index fdaa3e799c2..c53aa783b7e 100644 --- a/python/oneflow/test/tensor/test_tensor_indexing.py +++ b/python/oneflow/test/tensor/test_tensor_indexing.py @@ -23,6 +23,66 @@ import oneflow.unittest +def _test_numpy_scalar_indexing(test_case, numpy_x, np_scalar): + x = flow.Tensor(numpy_x) + + # basic_slice + test_case.assertTrue(np.allclose(numpy_x[np_scalar(1)], x[np_scalar(1)].numpy())) + test_case.assertTrue(np.allclose(numpy_x[np_scalar(-2)], x[np_scalar(-2)].numpy())) + test_case.assertTrue( + np.allclose( + numpy_x[np_scalar(0), np_scalar(1)], x[np_scalar(0), np_scalar(1)].numpy() + ) + ) + test_case.assertTrue( + np.allclose( + numpy_x[(np_scalar(0), np_scalar(1))], + x[(np_scalar(0), np_scalar(1))].numpy(), + ) + ) + test_case.assertTrue( + np.allclose( + numpy_x[((np_scalar(0), np_scalar(1)))], + x[((np_scalar(0), np_scalar(1)))].numpy(), + ) + ) + +def _test_numpy_scalar_advance_indexing(test_case, numpy_x, np_scalar): + x = flow.Tensor(numpy_x) + + # advance indexing + test_case.assertTrue( + np.allclose( + numpy_x[[np_scalar(0), np_scalar(1)]], + x[[np_scalar(0), np_scalar(1)]].numpy(), + ) + ) + test_case.assertTrue( + np.allclose( + numpy_x[[np_scalar(0), np_scalar(1)], [np_scalar(1), np_scalar(0)]], + x[[np_scalar(0), np_scalar(1)], [np_scalar(1), np_scalar(0)]].numpy(), + ) + ) + test_case.assertTrue( + np.allclose( + numpy_x[ + [ + [np_scalar(0), np_scalar(1)], + [np_scalar(0), np_scalar(1)], + [np_scalar(1), np_scalar(0)], + ] + ], + x[ + [ + [np_scalar(0), np_scalar(1)], + [np_scalar(0), np_scalar(1)], + [np_scalar(1), np_scalar(0)], + ] + ].numpy(), + ) + ) + + def _test_basic_slice(test_case, numpy_x): x = flow.tensor(numpy_x) @@ -278,6 +338,29 @@ def test_combining_indexing(test_case): numpy_x = np.arange(0, 720, 1).reshape([8, 9, 10]).astype(np.float32) _test_combining_indexing(test_case, numpy_x) + def test_numpy_scalar_indexing(test_case): + for np_scalar in [np.int8, np.int16, np.int32, np.int64]: + numpy_x = np.arange(0, 60, 1).reshape([3, 4, 5]).astype(np.float32) + _test_numpy_scalar_indexing(test_case, numpy_x, np_scalar) + + numpy_x = np.arange(0, 360, 1).reshape([3, 4, 5, 6]).astype(np.float32) + _test_numpy_scalar_indexing(test_case, numpy_x, np_scalar) + + numpy_x = np.arange(0, 720, 1).reshape([8, 9, 10]).astype(np.float32) + _test_numpy_scalar_indexing(test_case, numpy_x, np_scalar) + + # TODO: add np.int16 when advance indexing supports np.int16 mapping + for np_scalar in [np.int32, np.int64]: + numpy_x = np.arange(0, 60, 1).reshape([3, 4, 5]).astype(np.float32) + _test_numpy_scalar_advance_indexing(test_case, numpy_x, np_scalar) + + numpy_x = np.arange(0, 360, 1).reshape([3, 4, 5, 6]).astype(np.float32) + _test_numpy_scalar_advance_indexing(test_case, numpy_x, np_scalar) + + numpy_x = np.arange(0, 720, 1).reshape([8, 9, 10]).astype(np.float32) + _test_numpy_scalar_advance_indexing(test_case, numpy_x, np_scalar) + + def test_mask_getitem(test_case): numpy_x = np.arange(0, 60, 1).reshape([3, 4, 5]).astype(np.float32) _test_mask_getitem(test_case, numpy_x) From d79d35f314ad7bbd7029d2732c798e6773c029a7 Mon Sep 17 00:00:00 2001 From: oneflow-ci-bot Date: Wed, 6 Apr 2022 03:37:46 +0000 Subject: [PATCH 6/6] auto format by CI --- python/oneflow/test/tensor/test_tensor_indexing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/oneflow/test/tensor/test_tensor_indexing.py b/python/oneflow/test/tensor/test_tensor_indexing.py index c53aa783b7e..cb238798e13 100644 --- a/python/oneflow/test/tensor/test_tensor_indexing.py +++ b/python/oneflow/test/tensor/test_tensor_indexing.py @@ -47,9 +47,10 @@ def _test_numpy_scalar_indexing(test_case, numpy_x, np_scalar): ) ) + def _test_numpy_scalar_advance_indexing(test_case, numpy_x, np_scalar): x = flow.Tensor(numpy_x) - + # advance indexing test_case.assertTrue( np.allclose( @@ -348,7 +349,7 @@ def test_numpy_scalar_indexing(test_case): numpy_x = np.arange(0, 720, 1).reshape([8, 9, 10]).astype(np.float32) _test_numpy_scalar_indexing(test_case, numpy_x, np_scalar) - + # TODO: add np.int16 when advance indexing supports np.int16 mapping for np_scalar in [np.int32, np.int64]: numpy_x = np.arange(0, 60, 1).reshape([3, 4, 5]).astype(np.float32) @@ -359,7 +360,6 @@ def test_numpy_scalar_indexing(test_case): numpy_x = np.arange(0, 720, 1).reshape([8, 9, 10]).astype(np.float32) _test_numpy_scalar_advance_indexing(test_case, numpy_x, np_scalar) - def test_mask_getitem(test_case): numpy_x = np.arange(0, 60, 1).reshape([3, 4, 5]).astype(np.float32)