Associativity during backprop

Leonardo_Romor: <p>Hi,</p>
<p>Before trying to reinvent the wheel for my model I would like to check if something like the following<br>
is already implemented.<br>
Does the current implementation tries to perform the most optimal set of tensor product<br>
based on the size of parent/children tensors?<br>
A simple example is a product between the following matrices A * B* C = (100 x 100) * (100 x 10) * (10 x 1).<br>
If I’m correct this should be a relatively common operation, especially during the backward-pass<br>
due to the chain rule.<br>
Performing (A*B) * C should result in 200900 flops, while A * (B * C) only 21800.</p>

albanD: <p>Hi,</p>
<p>No we don’t actually try to do that.<br>
The main reason is that we almost never actually do such vector matrix product.<br>
The backward pass implementation is given v and computes <code>v^T Jf</code> with <code>Jf</code> being the Jacobian of the current function f.<br>
So we don’t actually need to materialize <code>Jf</code> ever.</p>
<p>For example, if you consider the relu operation for a 1D input of size n. The Jacobian would be an <code>n^2</code> matrix.<br>
But what the backward pass does is just mask out some entries in <code>v</code> as: <code>v[inp&lt;0] = 0</code>.</p>

Leonardo_Romor: <blockquote>
<p>For example, if you consider the relu operation for a 1D input of size n. The Jacobian would be an  <code>n^2</code>  matrix.<br>
But what the backward pass does is just mask out some entries in  <code>v</code>  as:  <code>v[inp&lt;0] = 0</code> .</p>
</blockquote>
<p>I see, but what if you have a convolution? Correct me if I’m wrong, but at that point, you would have to convolve the convolution output with the respective input region for taking the gradient w.r.t the weights right?</p>
<p>What if the weights are functions say of a tensor product of a smaller amount of parameters (Imagine a separable 2d convolution, where I decide to compute the kernel using the tensor product)? At that point, pytorch would first compute the gradient for each weight of the full 2d kernel, and then the result would be multiplied by the gradient of the tensor, which is similar to the (A * B) * C operation above, right?</p>
<p>Anyways, thank you for your time answering my question. If I see that the method above applies to my situation I definitely implement it.</p>

albanD: <p>Hi,</p>
<p>Yes in theory it does, but in practice, since none of these matrices are actually materialized, you might not get the speedup you would think by just looking at the matrix products.<br>
In particular the convolution backward is a transposed convolution which might be tricky to adapt to do what you want to do</p>

Leonardo_Romor: <p>What do you mean by none of these matrices are materialized?</p>

albanD: <p>I mean that the Jacobian matrix is never actually created. And even though this matrix is <code>n x n</code> in size, we can do the dot product with a vector of size <code>n</code> in time linear in <code>n</code> instead of <code>n^2</code> (using the sparsity of the matrix).<br>
But the logic you talk about above about optimizing matrix products is based on the idea that regular matrix product is <code>n^2</code> complexity.</p>
<p>I am not saying you cannot do it, but you might not see the benefits you expect.</p>

Leonardo_Romor: <p>I see, thank you for your answer and advice!</p>
<p>At this point is just out of curiosity.<br>
Even though torch doesn’t materialize those matrices, this amount of operations needs to be done one way or another. I guess what you mean, is that this matrix doesn’t appear as a contiguous array in memory, is that right? If not, I don’t know what would be the alternative. I would love to know more details if it doesn’t waste too much of your time on this specific matter.</p>

albanD: <aside class="quote no-group" data-username="Leonardo_Romor" data-post="7" data-topic="89639">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/leonardo_romor/48/77696_2.png" class="avatar"> Leonardo_Romor:</div>
<blockquote>
<p>this amount of operations needs to be done one way or another</p>
</blockquote>
</aside>
<p>If the matrix is sparse, you don’t actually have to <img src="https://discuss.pytorch.org/images/emoji/apple/smiley.png?v=14" title=":smiley:" class="emoji" alt=":smiley:" loading="lazy" width="20" height="20"></p>
<p>For example if you have a vector v of size n and a matrix A of size nxn.<br>
If you know that A is diagonal. You can perform the vector matrix product by just storing <code>a</code> the diagonal of <code>a</code>. And performing an element-wise product of the <code>n</code> elements of v and a.<br>
So you get the same results as <code>v^T A</code> but by doing <code>n</code> operations instead of <code>n^2</code>.</p>
<p>Does that make sense?</p>

Leonardo_Romor: <p>Yes, it makes sense, but I don’t understand the assumption of why the matrix would be sparse most of the cases. For old-fashioned convolutions/models/datasets, sparsity seems not so common if not “model induced”.<br>
So I guess that’s the reason why I couldn’t understand your advice <img src="https://discuss.pytorch.org/images/emoji/apple/slight_smile.png?v=9" title=":slight_smile:" class="emoji" alt=":slight_smile:"> .<br>
But maybe you are telling me that most of the feature maps are indeed sparse, and under the hood pytorch does lots of checks in this regard. So apart the first layer, everything else is sparsified.</p>

albanD: <p>Actually a convolution is very very sparse.<br>
In the setting where you consider the input as 1D and output as 1D where you just collapse all the dimensions, The Jacobian would be huge (or even writing the convolution as a mm with 1D inputs).<br>
But the weights are both re-used and applied only to subsets of the channels. Leading to a significant sparsity in that operation.</p>

Leonardo_Romor: <p>I see what you mean. Thank you for your time!</p>