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
Enable product for bool tensor #48637
Conversation
💊 CI failures summary and remediationsAs of commit 21bd03a (more details on the Dr. CI page): 💚 💚 Looks good so far! There are no failures yet. 💚 💚 This comment was automatically generated by Dr. CI (expand for details).Follow this link to opt-out of these comments for your Pull Requests.Please report bugs/suggestions to the (internal) Dr. CI Users group. This comment has been revised 46 times. |
7e3937f
to
220e51d
Compare
220e51d
to
f4d2d18
Compare
Codecov Report
@@ Coverage Diff @@
## master #48637 +/- ##
==========================================
- Coverage 80.62% 80.62% -0.01%
==========================================
Files 1875 1875
Lines 202769 202769
==========================================
- Hits 163486 163479 -7
- Misses 39283 39290 +7 |
test/test_reductions.py
Outdated
@@ -814,6 +814,17 @@ def test_prod(self, device, dtype): | |||
torch.prod(x, 1, out=res2) | |||
self.assertEqual(res1, res2) | |||
|
|||
def test_prod_bool(self, device): | |||
tests = [ | |||
([True, True], True), |
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.
Add an empty tensor input, too, and instead of comparing with expected create a NumPy array from the inputs and compare with the result of np.prod
.
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.
Addressed! Thanks for suggestion!
test/test_reductions.py
Outdated
|
||
for val, exp in tests: | ||
tensor = torch.tensor(val, device=device) | ||
self.assertEqual(torch.prod(tensor, dtype=torch.bool), torch.tensor(exp, device=device)) |
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.
What happens if dtype isn't set? That is, what is the default behavior when a bool tensor is passed to prod?
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.
In this case (a bool tensor passed to prod without dtype
variate), an int64
type Tensor would be returned, which is consistent with the performance in NumPy.
>>> np.prod(np.array([True,True]))
1
>>> torch.prod(torch.tensor([True, True]))
tensor(1)
>>> np.prod(np.array([True,True])).dtype
dtype('int64')
>>> torch.prod(torch.tensor([True, True])).dtype
torch.int64
The only difference is that for the input of an empty Tensor, the result type of PyTorch is float32
, while for NumPy, it is float64
. I think it's caused by the different default types between PyTorch and NumPy, so I guess it's just fine.
>>> np.prod(np.array([]))
1.0
>>> np.prod(np.array([])).dtype
dtype('float64')
>>> torch.prod(torch.tensor([]))
tensor(1.)
>>> torch.prod(torch.tensor([])).dtype
torch.float32
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.
If the returned dtype is int64 when a boolean tensor is given as input, which code path is exercised in the function for boolean tensors? Because won't iter.dtype()
return intt64?
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 think the iter.dtype()
here would return int64
. The code would finaly goes to prod_out_impl
, and it would use get_dtype
method to casting inputs from kBool
type to kLong
before the Iterator be created.
pytorch/aten/src/ATen/native/ReduceOps.cpp
Lines 426 to 430 in a20d451
ScalarType src_type = self.scalar_type(); | |
if (promote_integers && at::isIntegralType(src_type, /*includeBool=*/true)) { | |
return kLong; | |
} | |
return src_type; |
pytorch/aten/src/ATen/native/ReduceOps.cpp
Lines 489 to 492 in a20d451
static Tensor& prod_out_impl(Tensor& result, const Tensor& self, IntArrayRef dim, | |
bool keepdim, c10::optional<ScalarType> opt_dtype) { | |
ScalarType dtype = get_dtype(result, self, opt_dtype, true); | |
auto iter = make_reduction("prod", result, self, dim, keepdim, dtype); |
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.
That's my thinking, too. Would you verify? Also, if that's the case, then is this code ever used?
if (iter.dtype() == ScalarType::Bool) {
using scalar_t = bool;
binary_kernel_reduce_vec(
iter,
[=](scalar_t a, scalar_t b) -> scalar_t { return a && b; },
[=](Vec256<scalar_t> a, Vec256<scalar_t> b) { return a && b; },
/*identity=*/1);
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.
Yep, As shown below, I verified it in my environment.
When setting dtype as torch.bool
, it would using the code of a&&b
, and in other cases (including the above-mentioned default int64
scene), the part of a*b
would be used.
Such verification has been verified in both CPU and CUDA environments, and the performance of the two is consistent.
static void prod_kernel_impl(TensorIterator& iter) {
// Workaround for the error: '*' in boolean context, suggest '&&' instead [-Werror=int-in-bool -context]
if (iter.dtype() == ScalarType::Bool) {
std::cout << "Using a && b" << std::endl;
using scalar_t = bool;
binary_kernel_reduce_vec(
iter,
[=](scalar_t a, scalar_t b) -> scalar_t { return a && b; },
[=](Vec256<scalar_t> a, Vec256<scalar_t> b) { return a && b; },
/*identity=*/1);
} else {
std::cout << "Using a * b" << std::endl;
AT_DISPATCH_ALL_TYPES_AND_COMPLEX(iter.dtype(), "prod_cpu", [&] {
binary_kernel_reduce_vec(
iter,
[=](scalar_t a, scalar_t b) -> scalar_t { return a * b; },
[=](Vec256 <scalar_t> a, Vec256 <scalar_t> b) { return a * b; },
/*identity=*/1);
});
}
}
>>> import torch
>>> torch.prod(torch.tensor([True, True]), dtype=torch.bool)
Using a && b
tensor(True)
>>> torch.prod(torch.tensor([True, True]))
Using a * b
tensor(1)
>>> torch.prod(torch.tensor([True, True], device='cuda'), dtype=torch.bool)
Using a && b
tensor(True, device='cuda:0')
>>> torch.prod(torch.tensor([True, True]))
Using a * b
tensor(1)
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.
Thank you for the explanation, @Kiyosora, that's very helpful.
In the future we may want to become more clever with this dispatch so we can compute in bool when the input is bool, even if the output dtype is something else (like int64), but this seems OK for now.
I've made one suggestion about extending the test to validate both the default bool->int64 behavior is consistent with NumPy and the already tested bool->bool behavior is, too.
Hey @Kiyosora! Thanks for submitting this PR. Just a couple questions for you. |
425e462
to
dbadd20
Compare
Thanks for reviewing this PR, @mruberry ! 😃 |
@@ -814,6 +814,13 @@ def test_prod(self, device, dtype): | |||
torch.prod(x, 1, out=res2) | |||
self.assertEqual(res1, res2) | |||
|
|||
def test_prod_bool(self, device): | |||
vals = [[True, True], [True, False], [False, False], []] | |||
for val in vals: |
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.
Would you extend this test to verify bool->int64 consistent with NumPy, too?
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.
Addressed! Thanks for your suggestion!
233dbab
to
65ad982
Compare
f0aae22
to
f55f363
Compare
f55f363
to
21bd03a
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.
Cool! Thanks @Kiyosora!
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.
@mruberry has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
Summary: Fixes pytorch#48351 Pull Request resolved: pytorch#48637 Reviewed By: mrshenli Differential Revision: D25658596 Pulled By: mruberry fbshipit-source-id: ff3ada74b6d281c8e4753ed38339a1c036f722ee
Fixes #48351