From 1299deae7faa27b3b735eff9862a7893ebeca62a Mon Sep 17 00:00:00 2001 From: Yinggang Wang <wyg19970408@gmail.com> Date: Tue, 20 Jul 2021 07:06:18 -0500 Subject: [PATCH] Feat tensor stride property (#5543) * feat(Stride): add Stride class * feat(Tensor): support stride and storage_offset interface * feat(Tensor): add is_contiguous interface * remove test declaration * feat(TensorMeta): add hash and compare for stride * refine code * refine IsContiguous function Co-authored-by: oneflow-ci-bot <69100618+oneflow-ci-bot@users.noreply.github.com> --- oneflow/api/python/framework/tensor.cpp | 13 +++++ oneflow/core/common/shape_vec.h | 2 + oneflow/core/framework/stride.cpp | 51 ++++++++++++++++++ oneflow/core/framework/stride.h | 65 +++++++++++++++++++++++ oneflow/core/framework/tensor.h | 4 ++ oneflow/core/framework/tensor_impl.cpp | 12 ++++- oneflow/core/framework/tensor_impl.h | 4 ++ oneflow/core/framework/tensor_meta.h | 11 +++- oneflow/core/framework/tensor_method.cpp | 43 +++++++++++++++ oneflow/core/framework/tensor_method.h | 32 +++++++++++ oneflow/python/framework/tensor.py | 12 +++++ oneflow/python/test/tensor/test_tensor.py | 9 ++++ 12 files changed, 254 insertions(+), 4 deletions(-) create mode 100644 oneflow/core/framework/stride.cpp create mode 100644 oneflow/core/framework/stride.h create mode 100644 oneflow/core/framework/tensor_method.cpp create mode 100644 oneflow/core/framework/tensor_method.h diff --git a/oneflow/api/python/framework/tensor.cpp b/oneflow/api/python/framework/tensor.cpp index a9c191af0..a400a22df 100644 --- a/oneflow/api/python/framework/tensor.cpp +++ b/oneflow/api/python/framework/tensor.cpp @@ -22,7 +22,9 @@ limitations under the License. #include "oneflow/core/common/tensor_buffer.h" #include "oneflow/core/framework/instructions_builder.h" #include "oneflow/core/framework/tensor.h" +#include "oneflow/core/framework/tensor_method.h" #include "oneflow/core/framework/device.h" +#include "oneflow/core/framework/stride.h" #include "oneflow/core/framework/py_distribute.h" #include "oneflow/core/job/placement.cfg.h" #include "oneflow/core/job/global_for.h" @@ -201,6 +203,10 @@ void ApiRegisterTensorHook(const std::shared_ptr<Tensor>& self, const AutogradMe return RegisterTensorHook(self, hook).GetOrThrow(); } +bool ApiIsContiguous(const std::shared_ptr<Tensor>& tensor) { + return IsContiguous(tensor).GetOrThrow(); +} + } // namespace ONEFLOW_API_PYBIND11_MODULE("", m) { @@ -219,6 +225,13 @@ ONEFLOW_API_PYBIND11_MODULE("", m) { return std::shared_ptr<Tensor>(); } }) + .def("storage_offset", [](const Tensor& t) { return t.storage_offset().GetOrThrow(); }) + .def("stride", + [](const Tensor& t) { + const auto& stride = t.stride().GetPtrOrThrow()->StrideVec(); + return py::tuple(py::make_iterator(stride.begin(), stride.end())); + }) + .def("is_contiguous", &ApiIsContiguous) // setter of grad .def("set_grad", [](Tensor& t, const std::shared_ptr<Tensor>& grad) { diff --git a/oneflow/core/common/shape_vec.h b/oneflow/core/common/shape_vec.h index c97870a48..541851f3e 100644 --- a/oneflow/core/common/shape_vec.h +++ b/oneflow/core/common/shape_vec.h @@ -27,11 +27,13 @@ namespace oneflow { typedef std::vector<int64_t> DimVector; typedef std::vector<int64_t> AxisVector; +typedef std::vector<int64_t> StrideVector; #else typedef fixed_vector<int64_t, SHAPE_MAX_AXIS_SIZE> DimVector; typedef fixed_vector<int64_t, SHAPE_MAX_AXIS_SIZE> AxisVector; +typedef fixed_vector<int64_t, SHAPE_MAX_AXIS_SIZE> StrideVector; #endif } // namespace oneflow diff --git a/oneflow/core/framework/stride.cpp b/oneflow/core/framework/stride.cpp new file mode 100644 index 000000000..1136ede86 --- /dev/null +++ b/oneflow/core/framework/stride.cpp @@ -0,0 +1,51 @@ +/* +Copyright 2020 The OneFlow Authors. All rights reserved. + +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 "oneflow/core/framework/stride.h" + +namespace oneflow { + +Stride::Stride(const Shape& shape) { + if (shape.NumAxes() > 0) { + stride_vec_.resize(shape.NumAxes()); + int64_t stride = 1; + for (size_t i = shape.NumAxes(); i > 0; --i) { + stride_vec_.at(i - 1) = stride; + stride *= shape.At(i - 1); + } + } +} + +Stride& Stride::operator=(const Stride& stride) { + stride_vec_ = stride.stride_vec_; + return *this; +} + +bool Stride::operator==(const Stride& rhs) const { return stride_vec_ == rhs.stride_vec_; } + +std::string Stride::ToString() const { + std::stringstream ss; + int32_t idx = 0; + ss << "("; + for (int64_t dim : stride_vec_) { + ss << dim; + if (++idx != stride_vec_.size() || stride_vec_.size() == 1) { ss << ","; } + } + ss << ")"; + return ss.str(); +} + +} // namespace oneflow diff --git a/oneflow/core/framework/stride.h b/oneflow/core/framework/stride.h new file mode 100644 index 000000000..2fbfc114f --- /dev/null +++ b/oneflow/core/framework/stride.h @@ -0,0 +1,65 @@ +/* +Copyright 2020 The OneFlow Authors. All rights reserved. + +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. +*/ + +#ifndef ONEFLOW_CORE_FRAMEWORK_STRIDE_H_ +#define ONEFLOW_CORE_FRAMEWORK_STRIDE_H_ + +#include "oneflow/core/common/shape.h" +#include "oneflow/core/common/util.h" + +namespace oneflow { + +class Stride final { + public: + Stride() = default; + explicit Stride(const Shape& shape); + explicit Stride(const StrideVector& stride_vec) : stride_vec_(stride_vec) {} + explicit Stride(StrideVector&& stride_vec) : stride_vec_(stride_vec) {} + Stride(const std::initializer_list<int64_t>& stride_vec) : stride_vec_(stride_vec) {} + Stride& operator=(const Stride& stride); + ~Stride() = default; + + bool operator==(const Stride& rhs) const; + bool operator!=(const Stride& rhs) const { return !(*this == rhs); } + + std::string ToString() const; + + // Getters and Setters + const StrideVector& StrideVec() const { return stride_vec_; } + int64_t NumAxes() const { return stride_vec_.size(); } + int64_t At(int64_t index) const { return stride_vec_.at(index); } + void Set(int64_t index, int64_t val) { stride_vec_.at(index) = val; } + + private: + StrideVector stride_vec_; +}; + +} // namespace oneflow + +namespace std { + +template<> +struct hash<oneflow::Stride> { + size_t operator()(const oneflow::Stride& stride) const { + size_t ret = 0; + FOR_RANGE(int, i, 0, stride.NumAxes()) { ret ^= std::hash<int64_t>()(stride.At(i)); } + return ret; + } +}; + +} // namespace std + +#endif // ONEFLOW_CORE_FRAMEWORK_STRIDE_H_ diff --git a/oneflow/core/framework/tensor.h b/oneflow/core/framework/tensor.h index 33485262f..7c614186a 100644 --- a/oneflow/core/framework/tensor.h +++ b/oneflow/core/framework/tensor.h @@ -64,6 +64,8 @@ class Tensor { virtual Maybe<VmLocalDepObject> compute_local_dep_object() const = 0; virtual Maybe<bool> has_eager_blob_object() const = 0; virtual Maybe<TensorStorage> tensor_storage() const { OF_UNIMPLEMENTED(); } + virtual Maybe<const Stride> stride() const { OF_UNIMPLEMENTED(); } + virtual Maybe<int64_t> storage_offset() const { OF_UNIMPLEMENTED(); } // Getters/Setters valid only for EagerConsistentTensor virtual Maybe<Symbol<cfg::ParallelDistribution>> consumer_parallel_distribution_constraint() @@ -165,6 +167,8 @@ class MirroredTensor final : public TensorIf<MirroredTensor>, } Maybe<TensorStorage> tensor_storage() const override { return impl_->tensor_storage(); } Maybe<bool> has_eager_blob_object() const override { return impl_->has_eager_blob_object(); } + Maybe<const Stride> stride() const override { return impl_->stride(); } + Maybe<int64_t> storage_offset() const override { return impl_->storage_offset(); } // Getters for autograd Maybe<Tensor> acc_grad() const override { return impl_->acc_grad(); } diff --git a/oneflow/core/framework/tensor_impl.cpp b/oneflow/core/framework/tensor_impl.cpp index cbe7a41dc..0026ed0ff 100644 --- a/oneflow/core/framework/tensor_impl.cpp +++ b/oneflow/core/framework/tensor_impl.cpp @@ -19,6 +19,7 @@ limitations under the License. #include "oneflow/core/framework/instructions_builder.h" #include "oneflow/core/framework/tensor_impl.h" #include "oneflow/core/framework/tensor.h" +#include "oneflow/core/framework/stride.h" #include "oneflow/core/job/parallel_desc.h" #include "oneflow/core/job/sbp_parallel.cfg.h" #include "oneflow/core/framework/device.h" @@ -165,16 +166,23 @@ Maybe<MirroredTensorImpl> EagerMirroredTensorImpl::detach() const { return std::shared_ptr<MirroredTensorImpl>(detached_impl); } +MirroredTensorMeta::MirroredTensorMeta(const std::shared_ptr<const Shape>& shape, DataType dtype, + Symbol<Device> device) + : TensorMeta(shape, dtype), + device_(device), + stride_(std::make_shared<const Stride>(*shape)), + storage_offset_(0) {} + bool MirroredTensorMeta::operator==(const MirroredTensorMeta& other) const { // It's correct to ignore is_dynamic_ field. return *this->shape_ptr() == *other.shape_ptr() && this->dtype() == other.dtype() - && *this->device() == *other.device(); + && *this->device() == *other.device() && this->stride() == other.stride(); } size_t MirroredTensorMeta::CalcHashValue() const { // It's correct to ignore is_dynamic_ field. return std::hash<Shape>()(*shape_ptr()) ^ std::hash<DataType>()(dtype()) - ^ std::hash<Device>()(*device()); + ^ std::hash<Device>()(*device()) ^ std::hash<Stride>()(stride()); } bool ConsistentTensorMeta::operator==(const ConsistentTensorMeta& other) const { diff --git a/oneflow/core/framework/tensor_impl.h b/oneflow/core/framework/tensor_impl.h index ffa73c5d5..1a713e7aa 100644 --- a/oneflow/core/framework/tensor_impl.h +++ b/oneflow/core/framework/tensor_impl.h @@ -63,6 +63,8 @@ class TensorImpl { virtual Maybe<VmLocalDepObject> compute_local_dep_object() const = 0; virtual Maybe<TensorStorage> tensor_storage() const { OF_UNIMPLEMENTED(); } virtual Maybe<bool> has_eager_blob_object() const = 0; + virtual Maybe<const Stride> stride() const { OF_UNIMPLEMENTED(); } + virtual Maybe<int64_t> storage_offset() const { OF_UNIMPLEMENTED(); } // Getters for autograd Maybe<Tensor> acc_grad() const; @@ -210,6 +212,8 @@ class EagerMirroredTensorImpl final : public MirroredTensorImpl { return tensor_storage_; } Maybe<bool> has_eager_blob_object() const override { return eager_blob_object_.get(); } + Maybe<const Stride> stride() const override { return tensor_meta_->stride_ptr(); } + Maybe<int64_t> storage_offset() const override { return tensor_meta_->storage_offset(); } // Setters TensorStorage* mut_tensor_storage() { return tensor_storage_.get(); } diff --git a/oneflow/core/framework/tensor_meta.h b/oneflow/core/framework/tensor_meta.h index eea2b8f9a..2c1a5747e 100644 --- a/oneflow/core/framework/tensor_meta.h +++ b/oneflow/core/framework/tensor_meta.h @@ -27,6 +27,7 @@ class ParallelDistribution; class Shape; class Device; +class Stride; class ParallelDesc; namespace one { @@ -63,19 +64,25 @@ class TensorMeta : public user_op::TensorDesc { class MirroredTensorMeta : public TensorMeta { public: MirroredTensorMeta(const std::shared_ptr<const Shape>& shape, DataType dtype, - Symbol<Device> device) - : TensorMeta(shape, dtype), device_(device) {} + Symbol<Device> device); virtual ~MirroredTensorMeta() = default; const Symbol<Device>& device() const { return device_; } + const Stride& stride() const { return *stride_; } + const std::shared_ptr<const Stride>& stride_ptr() const { return stride_; } + int64_t storage_offset() const { return storage_offset_; } Symbol<Device>* mut_device() { return &device_; } + void set_stride(const std::shared_ptr<const Stride>& stride) { stride_ = stride; } + void set_storage_offset(int64_t offset) { storage_offset_ = offset; } bool operator==(const MirroredTensorMeta& other) const; size_t CalcHashValue() const; private: Symbol<Device> device_; + std::shared_ptr<const Stride> stride_; + int64_t storage_offset_; }; class ConsistentTensorMeta : public TensorMeta { diff --git a/oneflow/core/framework/tensor_method.cpp b/oneflow/core/framework/tensor_method.cpp new file mode 100644 index 000000000..3cdf87764 --- /dev/null +++ b/oneflow/core/framework/tensor_method.cpp @@ -0,0 +1,43 @@ +/* +Copyright 2020 The OneFlow Authors. All rights reserved. + +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 "oneflow/core/framework/tensor_method.h" +#include "oneflow/core/framework/stride.h" +#include "oneflow/core/common/shape.h" + +namespace oneflow { +namespace one { + +Maybe<bool> IsContiguous(const std::shared_ptr<Tensor>& tensor) { + const Shape& shape = *tensor->shape(); + const Stride& stride = *JUST(tensor->stride()); + int64_t dim = shape.NumAxes(); + int64_t expected_stride = 1; + bool contig_if_nonempty = true; + for (int64_t i = dim - 1; i >= 0; --i) { + // Contiguous by default when any dim is equal to zero + // https://stackoverflow.com/questions/31681324/identify-contiguous-segments-of-a-non-contiguous-numpy-array + if (shape.At(i) == 0) { return true; } + if (contig_if_nonempty && shape.At(i) != 1) { + if (stride.At(i) != expected_stride) { contig_if_nonempty = false; } + expected_stride *= shape.At(i); + } + } + return contig_if_nonempty; +} + +} // namespace one +} // namespace oneflow diff --git a/oneflow/core/framework/tensor_method.h b/oneflow/core/framework/tensor_method.h new file mode 100644 index 000000000..37caa763a --- /dev/null +++ b/oneflow/core/framework/tensor_method.h @@ -0,0 +1,32 @@ +/* +Copyright 2020 The OneFlow Authors. All rights reserved. + +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. +*/ + +#ifndef ONEFLOW_CORE_FRAMEWORK_TENSOR_METHOD_H_ +#define ONEFLOW_CORE_FRAMEWORK_TENSOR_METHOD_H_ + +#include "oneflow/core/framework/tensor.h" + +namespace oneflow { +namespace one { + +class Tensor; + +Maybe<bool> IsContiguous(const std::shared_ptr<Tensor>& tensor); + +} // namespace one +} // namespace oneflow + +#endif // ONEFLOW_CORE_FRAMEWORK_TENSOR_METHOD_H_ diff --git a/oneflow/python/framework/tensor.py b/oneflow/python/framework/tensor.py index 04dae678d..58230af9a 100644 --- a/oneflow/python/framework/tensor.py +++ b/oneflow/python/framework/tensor.py @@ -205,6 +205,18 @@ class Tensor: else: return self._undetermined_tensor.shape + def stride(self): + assert self.is_determined + return self._local_or_consistent_tensor.stride() + + def storage_offset(self): + assert self.is_determined + return self._local_or_consistent_tensor.storage_offset() + + def is_contiguous(self): + assert self.is_determined + return self._local_or_consistent_tensor.is_contiguous() + @property def device(self): if self._local_or_consistent_tensor is not None: diff --git a/oneflow/python/test/tensor/test_tensor.py b/oneflow/python/test/tensor/test_tensor.py index a7b666bec..8e9a08478 100644 --- a/oneflow/python/test/tensor/test_tensor.py +++ b/oneflow/python/test/tensor/test_tensor.py @@ -34,6 +34,15 @@ class TestTensor(flow.unittest.TestCase): np.array_equal(tensor.numpy(), np.ones(shape, dtype=np.float32)) ) + def test_tensor_property(test_case): + shape = (2, 3, 4, 5) + tensor = flow.Tensor(*shape) + tensor.determine() + test_case.assertEqual(tensor.storage_offset(), 0) + test_case.assertEqual(tensor.stride(), (60, 20, 5, 1)) + test_case.assertEqual(tensor.is_cuda, False) + test_case.assertTrue(tensor.is_contiguous()) + def test_copy_to_and_from_numpy(test_case): np_arr = np.array([4, 6], dtype=np.float32) tensor = flow.Tensor(np_arr, dtype=flow.float32) -- GitLab