-
Notifications
You must be signed in to change notification settings - Fork 3.9k
GH-47520: [C++][Tensor] Correct sparse tensor creation from dense tensor with negative zero #47586
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
base: main
Are you sure you want to change the base?
GH-47520: [C++][Tensor] Correct sparse tensor creation from dense tensor with negative zero #47586
Conversation
|
@rok, could you review this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've done a high over pass and posted some questions. I want to do a deeper pass in more details later this week.
The templating approach seems more maintainable. We should make sure everything is tested.
09c2259
to
8e7620f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update @andishgar !
I read through the logic and things look good to me. It's especially nice that element size calculations are no longer done by us. Some minor comments, but overall I think this is practically ready to merge.
I would now ask @pitrou to do a pass, especially on the template part.
8e7620f
to
c8c66e1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no further comments. I'll add this to the release milestone and hopefully @pitrou has time to review it before release. (Many Arrow contributors are going to PyData Paris next week so I'm not sure how long things will take.)
@pitrou could you per-chance review this before release? (we've already done multiple iterations) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I tried to read this PR but this is making many changes that do not seem related to the task of fixing a simple bug. Can we have a more minimal set of changes?
@pitrou Thanks for the feedback!
The issue is that the current zero-checking logic is incorrect across all sparse tensor creation paths, and that can lead to memory issues when tensors are created from negative zero values. So the fix touches several places that share this logic. Do you think it would still make sense to split it further? (This PR is already a breakdown of a larger patch |
Ok, I'll take another look here. |
@pitrou @rok diff --git a/cpp/src/arrow/sparse_tensor_test.cc b/cpp/src/arrow/sparse_tensor_test.cc
index c9c28a1..6aa5835 100644
--- a/cpp/src/arrow/sparse_tensor_test.cc
+++ b/cpp/src/arrow/sparse_tensor_test.cc
@@ -499,6 +499,8 @@ TYPED_TEST_P(TestFloatingSparseCOOTensorEquality, TestEquality) {
ASSERT_OK_AND_ASSIGN(st4, SparseCOOTensor::Make(*this->tensor2_));
EXPECT_FALSE(st4->Equals(*st4)); // same object
EXPECT_TRUE(st4->Equals(*st4, EqualOptions().nans_equal(true))); // same object
+ ASSERT_OK_AND_ASSIGN(auto my_tensor,st4->ToTensor());
+ ASSERT_TRUE(my_tensor->Equals(*this->tensor2_));
std::vector<c_value_type> values5 = this->values2_;
std::shared_ptr<SparseCOOTensor> st5;
@@ -955,6 +957,8 @@ TYPED_TEST_P(TestFloatingSparseCSRMatrixEquality, TestEquality) {
ASSERT_OK_AND_ASSIGN(st4, SparseCSRMatrix::Make(*this->tensor2_));
EXPECT_FALSE(st4->Equals(*st4)); // same object
EXPECT_TRUE(st4->Equals(*st4, EqualOptions().nans_equal(true))); // same object
+ ASSERT_OK_AND_ASSIGN(auto my_tensor,st4->ToTensor());
+ ASSERT_TRUE(my_tensor->Equals(*this->tensor2_));
std::vector<c_value_type> values5 = this->values2_;
std::shared_ptr<SparseCSRMatrix> st5;
@@ -1290,6 +1294,8 @@ TYPED_TEST_P(TestFloatingSparseCSCMatrixEquality, TestEquality) {
ASSERT_OK_AND_ASSIGN(st4, SparseCSCMatrix::Make(*this->tensor2_));
EXPECT_FALSE(st4->Equals(*st4)); // same object
EXPECT_TRUE(st4->Equals(*st4, EqualOptions().nans_equal(true))); // same object
+ ASSERT_OK_AND_ASSIGN(auto my_tensor,st4->ToTensor());
+ ASSERT_TRUE(my_tensor->Equals(*this->tensor2_));
std::vector<c_value_type> values5 = this->values2_;
std::shared_ptr<SparseCSCMatrix> st5;
@@ -1411,6 +1417,8 @@ TYPED_TEST_P(TestFloatingSparseCSFTensorEquality, TestEquality) {
ASSERT_OK_AND_ASSIGN(st4, SparseCSFTensor::Make(*this->tensor2_));
EXPECT_FALSE(st4->Equals(*st4)); // same object
EXPECT_TRUE(st4->Equals(*st4, EqualOptions().nans_equal(true))); // same object
+ ASSERT_OK_AND_ASSIGN(auto my_tensor,st4->ToTensor());
+ ASSERT_TRUE(my_tensor->Equals(*this->tensor2_));
c_value_type values5[2][3][4][5] = {};
std::copy_n(&this->values2_[0][0][0][0], this->length_ / sizeof(c_value_type), |
It depends, is the fix simple enough to be integrated here? |
@pitrou |
@andishgar how is |
@rok TYPED_TEST_P(TestFloatingSparseCOOTensorEquality, TestEquality) {
using ValueType = TypeParam;
using c_value_type = typename ValueType::c_type;
static_assert(is_floating_type<ValueType>::value, "Float type is required");
std::shared_ptr<SparseCOOTensor> st1, st2, st3;
ASSERT_OK_AND_ASSIGN(st1, SparseCOOTensor::Make(*this->tensor1_));
ASSERT_OK_AND_ASSIGN(st2, SparseCOOTensor::Make(*this->tensor2_));
ASSERT_OK_AND_ASSIGN(st3, SparseCOOTensor::Make(*this->tensor1_));
ASSERT_TRUE(st1->Equals(*st1));
ASSERT_FALSE(st1->Equals(*st2));
ASSERT_TRUE(st1->Equals(*st3));
// sparse tensors with NaNs
const c_value_type nan_value = static_cast<c_value_type>(NAN);
this->values2_[13] = nan_value;
EXPECT_TRUE(std::isnan(this->tensor2_->Value({1, 0, 1})));
std::shared_ptr<SparseCOOTensor> st4;
ASSERT_OK_AND_ASSIGN(st4, SparseCOOTensor::Make(*this->tensor2_));
EXPECT_FALSE(st4->Equals(*st4)); // same object
EXPECT_TRUE(st4->Equals(*st4, EqualOptions().nans_equal(true))); // same object
ASSERT_OK_AND_ASSIGN(auto my_tensor,st4->ToTensor());
ASSERT_TRUE(my_tensor->Equals(*this->tensor2_));
std::vector<c_value_type> values5 = this->values2_;
std::shared_ptr<SparseCOOTensor> st5;
std::shared_ptr<Buffer> buffer5 = Buffer::Wrap(values5);
NumericTensor<ValueType> tensor5(buffer5, this->shape_);
ASSERT_OK_AND_ASSIGN(st5, SparseCOOTensor::Make(tensor5));
EXPECT_FALSE(st4->Equals(*st5)); // different memory
EXPECT_TRUE(st4->Equals(*st5, EqualOptions().nans_equal(true))); // different memory
}
|
This only happens if you have a nan in the tensor? By default nans are not considered equal. |
@rok You’re right — I added some extra code to handle validation, and the problem is solved. |
a90bd1e
to
9e98736
Compare
I'll look at this first thing tomorrow. |
Rationale for this change
As mentioned here case 1.
What changes are included in this PR?
Handle negative zero in sparse tensor creation.
Are these changes tested?
Yes, I ran the relevant unit tests.
Are there any user-facing changes?
No.
This PR contains a "Critical Fix".
(If the changes fix either (a) a security vulnerability, (b) a bug that causes incorrect or invalid data to be produced, or (c) a bug that causes a crash—even when the API contract is upheld—please provide an explanation. If not, you can remove this.)
Reference: case 1