class_docstr: Graph attention layer from `Graph Attention Network
  <https://arxiv.org/pdf/1710.10903.pdf>`__, with the sparse aggregation
  accelerated by cugraph-ops.
  
  Parameters
  ----------
  in_feats : int or tuple
      Input feature size. A pair denotes feature sizes of source and
      destination nodes.
  out_feats : int
      Output feature size.
  num_heads : int
      Number of heads in multi-head attention.
  feat_drop : float, optional
      Dropout rate on feature. Defaults: ``0``.
  concat : bool, optional
      If False, the multi-head attentions are averaged instead of concatenated.
      Default: ``True``.
  edge_feats : int, optional
      Edge feature size. Default: ``None``.
  negative_slope : float, optional
      LeakyReLU angle of negative slope. Defaults: ``0.2``.
  residual : bool, optional
      If True, use residual connection. Defaults: ``False``.
  allow_zero_in_degree : bool, optional
      If there are 0-in-degree nodes in the graph, output for those nodes will
      be invalid since no message will be passed to those nodes. This is
      harmful for some applications causing silent performance regression.
      This module will raise a DGLError if it detects 0-in-degree nodes in
      input graph. By setting ``True``, it will suppress the check and let the
      users handle it by themselves. Defaults: ``False``.
  bias : bool, optional
      If True, learns a bias term. Defaults: ``True``.
  
  Examples
  --------
  >>> import dgl
  >>> import torch
  >>> from cugraph_dgl.nn import GATConv
  ...
  >>> device = 'cuda'
  >>> g = dgl.graph(([0,1,2,3,2,5], [1,2,3,4,0,3])).to(device)
  >>> g = dgl.add_self_loop(g)
  >>> feat = torch.ones(6, 10).to(device)
  >>> conv = GATConv(10, 2, num_heads=3).to(device)
  >>> res = conv(g, feat)
  >>> res
  tensor([[[ 0.2340,  1.9226],
          [ 1.6477, -1.9986],
          [ 1.1138, -1.9302]],
          [[ 0.2340,  1.9226],
          [ 1.6477, -1.9986],
          [ 1.1138, -1.9302]],
          [[ 0.2340,  1.9226],
          [ 1.6477, -1.9986],
          [ 1.1138, -1.9302]],
          [[ 0.2340,  1.9226],
          [ 1.6477, -1.9986],
          [ 1.1138, -1.9302]],
          [[ 0.2340,  1.9226],
          [ 1.6477, -1.9986],
          [ 1.1138, -1.9302]],
          [[ 0.2340,  1.9226],
          [ 1.6477, -1.9986],
          [ 1.1138, -1.9302]]], device='cuda:0', grad_fn=<ViewBackward0>)
docstr: Forward computation.
  
  Parameters
  ----------
  graph : DGLGraph or SparseGraph
      The graph.
  nfeat : torch.Tensor
      Input features of shape :math:`(N, D_{in})`.
  efeat: torch.Tensor, optional
      Optional edge features.
  max_in_degree : int
      Maximum in-degree of destination nodes. When :attr:`g` is generated
      from a neighbor sampler, the value should be set to the corresponding
      :attr:`fanout`. This option is used to invoke the MFG-variant of
      cugraph-ops kernel.
  
  Returns
  -------
  torch.Tensor
      The output feature of shape :math:`(N, H, D_{out})` where
      :math:`H` is the number of heads, and :math:`D_{out}` is size of
      output feature.
docstr_tok: ["Forward","computation",".","Parameters","--","--","--","--","--","graph",":","DGLGraph","or","SparseGraph","The","graph",".","nfeat",":","torch.Tensor","Input","features","of","shape",":","math",":","`","(","N",",","D_","{","in","}",")","`",".","efeat",":","torch.Tensor",",","optional","Optional","edge","features",".","max_in_degree",":","int","Maximum","in-degree","of","destination","nodes",".","When",":","attr",":","`","g","`","is","generated","from","a","neighbor","sampler",",","the","value","should","be","set","to","the","corresponding",":","attr",":","`","fanout","`",".","This","option","is","used","to","invoke","the","MFG-variant","of","cugraph-ops","kernel",".","Returns","--","--","--","-","torch.Tensor","The","output","feature","of","shape",":","math",":","`","(","N",",","H",",","D_","{","out","}",")","`","where",":","math",":","`","H","`","is","the","number","of","heads",",","and",":","math",":","`","D_","{","out","}","`","is","size","of","output","feature","."]
code: def forward(
      self,
      g: Union[SparseGraph, dgl.DGLHeteroGraph],
      nfeat: Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]],
      efeat: Optional[torch.Tensor] = None,
      max_in_degree: Optional[int] = None,
  ) -> torch.Tensor:
      r"""Forward computation.
  
      Parameters
      ----------
      graph : DGLGraph or SparseGraph
          The graph.
      nfeat : torch.Tensor
          Input features of shape :math:`(N, D_{in})`.
      efeat: torch.Tensor, optional
          Optional edge features.
      max_in_degree : int
          Maximum in-degree of destination nodes. When :attr:`g` is generated
          from a neighbor sampler, the value should be set to the corresponding
          :attr:`fanout`. This option is used to invoke the MFG-variant of
          cugraph-ops kernel.
  
      Returns
      -------
      torch.Tensor
          The output feature of shape :math:`(N, H, D_{out})` where
          :math:`H` is the number of heads, and :math:`D_{out}` is size of
          output feature.
      """
      if isinstance(g, dgl.DGLHeteroGraph):
          if not self.allow_zero_in_degree:
              if (g.in_degrees() == 0).any():
                  raise dgl.base.DGLError(
                      "There are 0-in-degree nodes in the graph, "
                      "output for those nodes will be invalid. "
                      "This is harmful for some applications, "
                      "causing silent performance regression. "
                      "Adding self-loop on the input graph by "
                      "calling `g = dgl.add_self_loop(g)` will resolve "
                      "the issue. Setting ``allow_zero_in_degree`` "
                      "to be `True` when constructing this module will "
                      "suppress the check and let the code run."
                  )
  
      bipartite = isinstance(nfeat, (list, tuple))
  
      _graph = self.get_cugraph_ops_CSC(
          g, is_bipartite=bipartite, max_in_degree=max_in_degree
      )
  
      if bipartite:
          nfeat = (self.feat_drop(nfeat[0]), self.feat_drop(nfeat[1]))
          nfeat_dst_orig = nfeat[1]
      else:
          nfeat = self.feat_drop(nfeat)
          nfeat_dst_orig = nfeat[: g.num_dst_nodes()]
  
      if efeat is not None:
          if self.lin_edge is None:
              raise RuntimeError(
                  f"{self.__class__.__name__}.edge_feats must be set to "
                  f"accept edge features."
              )
          efeat = self.lin_edge(efeat)
  
      if bipartite:
          if not hasattr(self, "lin_src"):
              raise RuntimeError(
                  f"{self.__class__.__name__}.in_feats must be a pair of "
                  f"integers to allow bipartite node features, but got "
                  f"{self.in_feats}."
              )
          nfeat_src = self.lin_src(nfeat[0])
          nfeat_dst = self.lin_dst(nfeat[1])
      else:
          if not hasattr(self, "lin"):
              raise RuntimeError(
                  f"{self.__class__.__name__}.in_feats is expected to be an "
                  f"integer, but got {self.in_feats}."
              )
          nfeat = self.lin(nfeat)
  
      out = ops_torch.operators.mha_gat_n2n(
          (nfeat_src, nfeat_dst) if bipartite else nfeat,
          self.attn_weights,
          _graph,
          num_heads=self.num_heads,
          activation="LeakyReLU",
          negative_slope=self.negative_slope,
          concat_heads=self.concat,
          edge_feat=efeat,
      )[: g.num_dst_nodes()]
  
      if self.concat:
          out = out.view(-1, self.num_heads, self.out_feats)
  
      if self.residual:
          res = self.lin_res(nfeat_dst_orig).view(
              -1, self.num_heads, self.out_feats
          )
          if not self.concat:
              res = res.mean(dim=1)
          out = out + res
  
      if self.bias is not None:
          out = out + self.bias
  
      return out
  
imports: from typing import Optional, Tuple, Union
  from cugraph_dgl.nn.conv.base import BaseConv, SparseGraph
  from cugraph.utilities.utils import import_optional
label_desc: Use image node_id 3 for calling the GATConv obj's underlying member method code with example usage: obj.forward(g, nfeat, efeat, max_in_degree) and returns: out