Why is my computation graph giving 0 gradients and how can I debug it?

MartensCedric: <p>I have this code, which is based off <a href="https://github.com/DCurro/CannyEdgePytorch" class="inline-onebox" rel="noopener nofollow ugc">GitHub - DCurro/CannyEdgePytorch</a></p>
<pre data-code-wrap="python"><code class="lang-python">import torch
import torch.nn as nn
import numpy as np
from scipy import signal

# https://github.com/DCurro/CannyEdgePytorch/blob/master/canny.py
class CannyNet(nn.Module):
    def __init__(self, threshold=8, use_cuda=False):
        super(CannyNet, self).__init__()

        self.threshold = threshold
        self.use_cuda = use_cuda

        filter_size = 5
        generated_filters = signal.windows.gaussian(filter_size,std=1.0).reshape([1,filter_size])
        self.gaussian_filter_horizontal = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(1,filter_size), padding=(0,filter_size//2))
        self.gaussian_filter_horizontal.weight.data.copy_(torch.from_numpy(generated_filters))
        self.gaussian_filter_horizontal.bias.data.copy_(torch.from_numpy(np.array([0.0])))
        self.gaussian_filter_vertical = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(filter_size,1), padding=(filter_size//2,0))
        self.gaussian_filter_vertical.weight.data.copy_(torch.from_numpy(generated_filters.T))
        self.gaussian_filter_vertical.bias.data.copy_(torch.from_numpy(np.array([0.0])))

        sobel_filter = np.array([[1, 0, -1],
                                 [2, 0, -2],
                                 [1, 0, -1]])

        self.sobel_filter_horizontal = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=sobel_filter.shape, padding=sobel_filter.shape[0]//2)
        self.sobel_filter_horizontal.weight.data.copy_(torch.from_numpy(sobel_filter))
        self.sobel_filter_horizontal.bias.data.copy_(torch.from_numpy(np.array([0.0])))
        self.sobel_filter_vertical = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=sobel_filter.shape, padding=sobel_filter.shape[0]//2)
        self.sobel_filter_vertical.weight.data.copy_(torch.from_numpy(sobel_filter.T))
        self.sobel_filter_vertical.bias.data.copy_(torch.from_numpy(np.array([0.0])))

        # filters were flipped manually
        filter_0 = np.array([   [ 0, 0, 0],
                                [ 0, 1, -1],
                                [ 0, 0, 0]])

        filter_45 = np.array([  [0, 0, 0],
                                [ 0, 1, 0],
                                [ 0, 0, -1]])

        filter_90 = np.array([  [ 0, 0, 0],
                                [ 0, 1, 0],
                                [ 0,-1, 0]])

        filter_135 = np.array([ [ 0, 0, 0],
                                [ 0, 1, 0],
                                [-1, 0, 0]])

        filter_180 = np.array([ [ 0, 0, 0],
                                [-1, 1, 0],
                                [ 0, 0, 0]])

        filter_225 = np.array([ [-1, 0, 0],
                                [ 0, 1, 0],
                                [ 0, 0, 0]])

        filter_270 = np.array([ [ 0,-1, 0],
                                [ 0, 1, 0],
                                [ 0, 0, 0]])

        filter_315 = np.array([ [ 0, 0, -1],
                                [ 0, 1, 0],
                                [ 0, 0, 0]])

        all_filters = np.stack([filter_0, filter_45, filter_90, filter_135, filter_180, filter_225, filter_270, filter_315])

        self.directional_filter = nn.Conv2d(in_channels=1, out_channels=8, kernel_size=filter_0.shape, padding=filter_0.shape[-1] // 2)
        self.directional_filter.weight.data.copy_(torch.from_numpy(all_filters[:, None, ...]))
        self.directional_filter.bias.data.copy_(torch.from_numpy(np.zeros(shape=(all_filters.shape[0],))))

    def forward(self, img):

        blur_horizontal = self.gaussian_filter_horizontal(img)
        self.blurred_img = self.gaussian_filter_vertical(blur_horizontal)

        self.grad_x = self.sobel_filter_horizontal(self.blurred_img)
        self.grad_y = self.sobel_filter_vertical(self.blurred_img)

        # COMPUTE THICK EDGES


        self.grad_mag = torch.sqrt(self.grad_x**2 + self.grad_y**2)
        self.grad_orientation = (torch.atan2(self.grad_y, self.grad_x) * (180.0/3.14159))
        self.grad_orientation += 180.0
        self.grad_orientation =  torch.round( self.grad_orientation / 45.0 ) * 45.0

        # THIN EDGES (NON-MAX SUPPRESSION)

        all_filtered = self.directional_filter(self.grad_mag)

        indices_positive = (self.grad_orientation / 45) % 8
        indices_negative = ((self.grad_orientation / 45) + 4) % 8


        height = indices_positive.size()[1]
        width = indices_positive.size()[2]
        pixel_count = height * width
        pixel_range = torch.FloatTensor([range(pixel_count)])
        if self.use_cuda:
            pixel_range = torch.cuda.FloatTensor([range(pixel_count)])


        indices = (indices_positive.view(-1).data * pixel_count + pixel_range).squeeze()

        channel_select_filtered_positive = all_filtered.view(-1)[indices.long()].view(1,height,width)

        indices = (indices_negative.view(-1).data * pixel_count + pixel_range).squeeze()
        channel_select_filtered_negative = all_filtered.view(-1)[indices.long()].view(1,height,width)

        channel_select_filtered = torch.stack([channel_select_filtered_positive,channel_select_filtered_negative])

        is_max = channel_select_filtered.min(dim=0)[0] &gt; 0.0
        is_max = is_max.squeeze()

        self.thin_edges = self.grad_mag.clone().squeeze()
        #self.thin_edges[is_max==0] = 0.0

        # THRESHOLD

        self.thresholded = self.thin_edges.clone()
        self.thresholded[self.thin_edges&lt;self.threshold] = 0.0

        self.early_threshold = self.grad_mag.clone()
        self.early_threshold[self.grad_mag&lt;self.threshold] = 0.0

        #print(grad_mag.size(), grad_orientation.size(), thin_edges.size(), thresholded.size(), early_threshold.size())
        #assert grad_mag.size() == grad_orientation.size() == thin_edges.size() == thresholded.size() == early_threshold.size()

        return self.blurred_img, self.grad_mag, self.grad_orientation, self.thin_edges, self.thresholded, self.early_threshold

...

blurred_img, grad_mag, grad_orientation, thin_edges, thresholded, early_threshold = canny_net(images_predicted)
predicted_silhouette = thresholded
loss_silhouette = ((predicted_silhouette - unnormalized_ref) ** 2).mean()
canny_net.grad_x.retain_grad()
canny_net.grad_y.retain_grad()
canny_net.blurred_img.retain_grad()
canny_net.grad_mag.retain_grad()
canny_net.thin_edges.retain_grad()
canny_net.thresholded.retain_grad()
canny_net.early_threshold.retain_grad()
predicted_silhouette.retain_grad()
deform_verts.retain_grad()
loss_silhouette.backward()
print(np.max(np.abs(deform_verts.grad.cpu().detach().numpy())))
print(np.max(np.abs(canny_net.blurred_img.grad.cpu().detach().numpy())))
print(np.max(np.abs(canny_net.grad_x.grad.cpu().detach().numpy())))
print(np.max(np.abs(canny_net.grad_y.grad.cpu().detach().numpy())))
print(np.max(np.abs(canny_net.grad_mag.grad.cpu().detach().numpy())))
print(np.max(np.abs(canny_net.thin_edges.grad.cpu().detach().numpy())))
print(np.max(np.abs(canny_net.thresholded.grad.cpu().detach().numpy())))
print(np.max(np.abs(predicted_silhouette.grad.cpu().detach().numpy())))
</code></pre>
<p>The output I can is:</p>
<blockquote>
<p>0.0<br>
0.0<br>
0.0<br>
0.0<br>
0.0<br>
0.0<br>
0.001063056<br>
0.001063056</p>
</blockquote>
<p>I am new to PyTorch and learning how to debug this. First, am I right to understand that <code>.grad</code> is dLoss/dparam? Then given that I commented out the operation for thin edges, it seems that dLoss/dGradMag is 0. Which seems very unlikely as a change in the gradient magnitude should affect the Loss. The loss is an L2 norm with a reference.  What could cause this issue and what are some tips to debug?</p>