Skip to content

[PyTorch] Store Tensor explicitly in IValue #48824

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
reduce code duplication on "[PyTorch] Store Tensor explicitly in IValue"
Enables following diff, which will make toTensor() return
`const Tensor&` and allow callers to avoid refcounting overhead.

Differential Revision: [D25324617](https://our.internmc.facebook.com/intern/diff/D25324617/)

[ghstack-poisoned]
  • Loading branch information
swolchok committed Dec 4, 2020
commit 0198db96571fd7c2e8165efb96aa9505e221dbb6
75 changes: 42 additions & 33 deletions aten/src/ATen/core/ivalue.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,23 +164,14 @@ struct CAFFE2_API IValue final {
}
}
IValue(IValue&& rhs) noexcept : tag(rhs.tag), is_intrusive_ptr(rhs.is_intrusive_ptr) {
if (isTensor()) {
new (&payload.as_tensor) at::Tensor(std::move(rhs.payload.as_tensor));
rhs.payload.as_tensor.~Tensor();
} else {
memcpy(&payload, &rhs.payload, sizeof(payload));
}
moveFrom(std::move(rhs));
rhs.tag = Tag::None;
rhs.is_intrusive_ptr = false;
}

/// @private [doxygen private]
~IValue() {
if (is_intrusive_ptr) {
c10::raw::intrusive_ptr::decref(payload.as_intrusive_ptr);
} else if (isTensor()) {
payload.as_tensor.~Tensor();
}
destroy();
}

// Always-inline for performance -- this gets called frequently
Expand All @@ -190,26 +181,8 @@ struct CAFFE2_API IValue final {
return *this;
}

// Tear down our state.
if (isTensor()) {
payload.as_tensor.~Tensor();
} else if (is_intrusive_ptr) {
c10::raw::intrusive_ptr::decref(payload.as_intrusive_ptr);
}

if (rhs.isTensor()) {
new (&payload.as_tensor) at::Tensor(std::move(rhs.payload.as_tensor));
rhs.payload.as_tensor.~Tensor();
} else {
// No need to specially handle rhs being an intrusive_ptr -- we
// steal the reference.
memcpy(&payload, &rhs.payload, sizeof(payload));
}

tag = rhs.tag;
is_intrusive_ptr = rhs.is_intrusive_ptr;
rhs.tag = Tag::None;
rhs.is_intrusive_ptr = false;
destroy();
moveFrom(std::move(rhs));
return *this;
}

Expand Down Expand Up @@ -330,7 +303,15 @@ struct CAFFE2_API IValue final {
std::swap(payload.as_tensor, rhs.payload.as_tensor);
} else if (isTensor()) {
at::Tensor t = std::move(payload.as_tensor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugh this is more involved than I had hoped. I guess it's UB to just relocate the Tensor without destructing and constructing again?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, you have to construct it in rhs.payload using placement new or it's UB. Skiping the destructor call is legit, and I'll probably try that.

payload.as_tensor.~Tensor();
// As far as I can tell, omitting the usual explicit destructor call
// is not UB in and of itself, and it's a slight perf win. The
// destructor is a no-op, because the moved-from Tensor is
// effectively an intrusive_ptr in the null state, so we don't need
// the behavior for correctness reasons either. Leaving this
// explanatory comment, including commented-out destructor call, to
// make this abundantly clear.
//
// payload.as_tensor.~Tensor();
memcpy(&payload, &rhs.payload, sizeof(payload));
new (&rhs.payload.as_tensor) at::Tensor(std::move(t));
} else if (rhs.isTensor()) {
Expand Down Expand Up @@ -860,7 +841,35 @@ struct CAFFE2_API IValue final {
class NullType = c10::detail::intrusive_target_default_null_type<T>>
c10::intrusive_ptr<T, NullType> toIntrusivePtr() const;

void clearToNone() {
void destroy() {
if (isTensor()) {
payload.as_tensor.~Tensor();
} else if (is_intrusive_ptr) {
c10::raw::intrusive_ptr::decref(payload.as_intrusive_ptr);
}
}

C10_ALWAYS_INLINE void moveFrom(IValue&& rhs) noexcept {
if (rhs.isTensor()) {
new (&payload.as_tensor) at::Tensor(std::move(rhs.payload.as_tensor));
// As far as I can tell, omitting the usual explicit destructor call
// is not UB in and of itself, and it's a slight perf win. The
// destructor is a no-op, because the moved-from Tensor is
// effectively an intrusive_ptr in the null state, so we don't need
// the behavior for correctness reasons either. Leaving this
// explanatory comment, including commented-out destructor call, to
// make this abundantly clear.
//
// rhs.payload.as_tensor.~Tensor();
} else {
memcpy(&payload, &rhs.payload, sizeof(payload));
}
tag = rhs.tag;
is_intrusive_ptr = rhs.is_intrusive_ptr;
rhs.clearToNone();
}

void clearToNone() noexcept {
payload.as_int = 0;
tag = Tag::None;
is_intrusive_ptr = false;
Expand Down
4 changes: 3 additions & 1 deletion aten/src/ATen/core/ivalue_inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ inline at::Tensor IValue::toTensor() && {
// effectively an intrusive_ptr in the null state, so we don't need
// the behavior for correctness reasons either. Leaving this
// explanatory comment, including commented-out destructor call, to
// make this abundantly clear. payload.as_tensor.~Tensor();
// make this abundantly clear.
//
// payload.as_tensor.~Tensor();
clearToNone();
return result;
}
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.