Skip to content

Implements Cumprod function for autograd#1439

Merged
apaszke merged 14 commits intopytorch:masterfrom
martinarjovsky:master
Jun 13, 2017
Merged

Implements Cumprod function for autograd#1439
apaszke merged 14 commits intopytorch:masterfrom
martinarjovsky:master

Conversation

@martinarjovsky
Copy link
Contributor

In reference to #1095 :)

The algorithm for nonzero elements is straightforward and pretty fast, but when zero elements
are present is pretty hard to do vectorized (and O(n^2) with n elements in the dimension of the input where the cumprod is done), hence the code is a bit complex.

A set of extra tests passed is present here https://gist.github.com/martinarjovsky/1d4679c54b2fc5cddf73cdd45b359c9f


if (input == 0).any():
ones = input.index_select(self.dim,
LT(range(1))).fill_(1).clone()

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

LT = torch.LongTensor

if (input == 0).any():
ones = input.index_select(self.dim,

This comment was marked as off-topic.

# At this point ommitted_products is the same size
# as input, except on the dimension dim where it's
# dim_size - k
assert ommitted_products.size()[self.dim] == dim_size - k, \

This comment was marked as off-topic.

torch.sum(grad_output * ommitted_products, dim=self.dim))

else:
output = torch.cumprod(input, dim=self.dim)

This comment was marked as off-topic.

This comment was marked as off-topic.


class Cumprod(Function):

def __init__(self, dim):

This comment was marked as off-topic.

LT(range(k))), dim=self.dim)

prods_from_k_plus_1 = torch.cumprod(input.index_select(
self.dim, LT(range(k+1, dim_size))), dim=self.dim)

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

LT(range(k))), dim=self.dim)

ommitted_products = prods_until_k
else: #k == 0

This comment was marked as off-topic.

else:
LT = torch.LongTensor

if (input == 0).any():

This comment was marked as off-topic.

prods_from_k_plus_1 = torch.cumprod(input.index_select(
self.dim, LT(range(k+1, dim_size))), dim=self.dim)

ommitted_products = prods_until_k.expand_as(

This comment was marked as off-topic.

This comment was marked as off-topic.

# as input, except on the dimension dim where it's
# dim_size - k
assert ommitted_products.size()[self.dim] == dim_size - k, \
"Dimension error"

This comment was marked as off-topic.

"Dimension error"

if k != 0:
size_to_expand = [l for l in input.size()]

This comment was marked as off-topic.

size_to_expand = [l for l in input.size()]
size_to_expand[self.dim] = k # Adding zeros to missing dimensions
size_to_expand = torch.Size(size_to_expand)
expanded_zeros = zeros.expand(size_to_expand)

This comment was marked as off-topic.

@fmassa
Copy link
Member

fmassa commented May 2, 2017

Did we consider using the implementation of cumsum to compute cumprod? We could writesomething like

def cumprod(x):
    return exp(cumsum(log(x)))

Maybe it's very unstable computing it like that?

@apaszke
Copy link
Contributor

apaszke commented May 2, 2017

@fmassa I'd expect it to be very very unstable for x close to 0 (+ cumprod is well defined when some values are 0, but your definition doesn't handle that case - log will give you -infs)

@fmassa
Copy link
Member

fmassa commented May 3, 2017

@apaszke I'm not so sure that it would be very very unstable for x close to 0, the forward pass doesn't seem to be that sensitive.
My implementation doesn't handle the cases where x <= 0, but that can be easily fixed I think.
Anyway, those are my 2 cents on the matter. :)

@martinarjovsky
Copy link
Contributor Author

martinarjovsky commented May 4, 2017

@fmassa This would have two problems.

  • It wouldn't support negative inputs.
  • It wouldn't support zero inputs.

The case with nonzero inputs (including x < 0) is handled in two lines already by doing

output = torch.cumprod(input, dim=self.dim)
return reverse_cumsum(output * grad_output, dim=self.dim) / input

It's the case with zeros that's hard :)

@martinarjovsky
Copy link
Contributor Author

Just fixed (or at least attempted) the issues. I added a detailed comment of the algorithm for the backward pass but it's likely way to much. Should I take it out and put it in the PR instead?

idx = torch.LongTensor(range(dim_size))

ones = input.select(self.dim, 0).unsqueeze(self.dim).clone().fill_(1)
zeros = ones.clone().fill_(0)

This comment was marked as off-topic.

This comment was marked as off-topic.

ret = torch.cumsum(-x, dim=dim)

end_idx = ret.size(dim) - 1
ret_sum = ret.narrow(dim, end_idx, 1)

This comment was marked as off-topic.

This comment was marked as off-topic.

return grad_input


def reverse_cumsum(x, dim):

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

self.dim
)

grad_input.index_copy_(

This comment was marked as off-topic.

else:
idx = torch.LongTensor(range(dim_size))

ones = input.select(self.dim, 0).unsqueeze(self.dim).clone().fill_(1)

This comment was marked as off-topic.

if input.is_cuda:
idx = torch.cuda.LongTensor(range(dim_size))
else:
idx = torch.LongTensor(range(dim_size))

This comment was marked as off-topic.

This comment was marked as off-topic.

ommitted_products = torch.cat(
(zeros.expand(size_to_expand), ommitted_products),
self.dim
)

This comment was marked as off-topic.

This comment was marked as off-topic.

@colesbury
Copy link
Member

@pytorchbot add to whitelist

@martinarjovsky
Copy link
Contributor Author

@apaszke Anything else I should do? :)

@apaszke
Copy link
Contributor

apaszke commented Jun 9, 2017

Sorry, we're all busy with v0.2 features. I'll try to review it soon and let you know, but it should be good.

input, = self.saved_tensors
dim_size = input.size(self.dim)
if dim_size == 1:
return grad_output.clone()

This comment was marked as off-topic.


ones_size = list(input.size())
ones_size[self.dim] = 1
ones = input.new().resize_(ones_size).fill_(1)

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

ones_size = list(input.size())
ones_size[self.dim] = 1
ones = input.new().resize_(ones_size).fill_(1)
zeros = ones * 0

This comment was marked as off-topic.

This comment was marked as off-topic.


grad_input.select(self.dim, k).copy_(torch.sum(
grad_output[dim_padding + (slice(k, None),)] * omitted_products,
dim=self.dim).squeeze())

This comment was marked as off-topic.

@apaszke apaszke merged commit 7c024e9 into pytorch:master Jun 13, 2017
@apaszke
Copy link
Contributor

apaszke commented Jun 13, 2017

Thank you!

jjsjann123 pushed a commit to jjsjann123/pytorch that referenced this pull request Mar 21, 2022
* initial volta support

* mma parallel type && cleanup

* cleanup

* alignment

* comment

* change request

* fix same parallel type

* move validation pass

* comment and cleanup

* lint

* comment and cleanup

* comment and format
jagadish-amd pushed a commit to jagadish-amd/pytorch that referenced this pull request Sep 5, 2024
* SWDEV-469009 - skips for flaky distributed tests

* Missed a file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants