code: def compute_conv_grad_sample(
      layer: Union[nn.Conv1d, nn.Conv2d, nn.Conv3d],
      activations: List[torch.Tensor],
      backprops: torch.Tensor,
  ) -> Dict[nn.Parameter, torch.Tensor]:
      """
      Computes per sample gradients for convolutional layers.
  
      Args:
          layer: Layer
          activations: Activations
          backprops: Backpropagations
      """
      activations = activations[0]
      n = activations.shape[0]
      if n == 0:
          # Empty batch
          ret = {}
          ret[layer.weight] = torch.zeros_like(layer.weight).unsqueeze(
              0
          )
          if layer.bias is not None and layer.bias.requires_grad:
              ret[layer.bias] = torch.zeros_like(layer.bias).unsqueeze(
                  0
              )
          return ret
  
      # get activations and backprops in shape depending on the Conv layer
      if type(layer) is nn.Conv2d:
          activations = unfold2d(
              activations,
              kernel_size=layer.kernel_size,
              padding=layer.padding,
              stride=layer.stride,
              dilation=layer.dilation,
          )
      elif type(layer) is nn.Conv1d:
          activations = activations.unsqueeze(-2)  # add the H dimension
          # set arguments to tuples with appropriate second element
          if layer.padding == "same":
              total_pad = layer.dilation[0] * (layer.kernel_size[0] - 1)
              left_pad = math.floor(total_pad / 2)
              right_pad = total_pad - left_pad
          elif layer.padding == "valid":
              left_pad, right_pad = 0, 0
          else:
              left_pad, right_pad = layer.padding[0], layer.padding[0]
          activations = F.pad(activations, (left_pad, right_pad))
          activations = torch.nn.functional.unfold(
              activations,
              kernel_size=(1, layer.kernel_size[0]),
              stride=(1, layer.stride[0]),
              dilation=(1, layer.dilation[0]),
          )
      elif type(layer) is nn.Conv3d:
          activations = unfold3d(
              activations,
              kernel_size=layer.kernel_size,
              padding=layer.padding,
              stride=layer.stride,
              dilation=layer.dilation,
          )
      backprops = backprops.reshape(n, -1, activations.shape[-1])
  
      ret = {}
      if layer.weight.requires_grad:
          # n=batch_sz; o=num_out_channels; p=(num_in_channels/groups)*kernel_sz
          grad_sample = contract("noq,npq->nop", backprops, activations)
          # rearrange the above tensor and extract diagonals.
          grad_sample = grad_sample.view(
              n,
              layer.groups,
              -1,
              layer.groups,
              int(layer.in_channels / layer.groups),
              np.prod(layer.kernel_size),
          )
          grad_sample = contract(
              "ngrg...->ngr...", grad_sample
          ).contiguous()
          shape = [n] + list(layer.weight.shape)
          ret[layer.weight] = grad_sample.view(shape)
  
      if layer.bias is not None and layer.bias.requires_grad:
          ret[layer.bias] = torch.sum(backprops, dim=2)
  
      return ret
  
file_path: opacus/opacus/grad_sample/conv.py
imports: import math
  from typing import Dict, List, Union
  import numpy
  import torch
  import torch.nn
  import torch.nn.functional
  from opacus.utils.tensor_utils import unfold2d, unfold3d
  from opt_einsum import contract
  from .utils import register_grad_sampler
label_desc: Use image node_id 1 for calling a global function with example usage: compute_conv_grad_sample(layer, activations, backprops) and returns: ret, ret