code: def forward(self, graph, feat):
      with graph.local_scope():
          if not self._allow_zero_in_degree:
              if (graph.in_degrees() == 0).any():
                  assert False
  
          if isinstance(feat, tuple):
              h_src = self.feat_drop(feat[0])
              h_dst = self.feat_drop(feat[1])
              if not hasattr(self, "fc_src"):
                  self.fc_src, self.fc_dst = self.fc, self.fc
              feat_src, feat_dst = h_src, h_dst
              feat_src = self.fc_src(h_src).view(
                  -1, self._num_heads, self._out_feats
              )
              feat_dst = self.fc_dst(h_dst).view(
                  -1, self._num_heads, self._out_feats
              )
          else:
              h_src = self.feat_drop(feat)
              feat_src = h_src
              feat_src = self.fc(h_src).view(
                  -1, self._num_heads, self._out_feats
              )
              if graph.is_block:
                  h_dst = h_src[: graph.number_of_dst_nodes()]
                  feat_dst = feat_src[: graph.number_of_dst_nodes()]
              else:
                  h_dst = h_src
                  feat_dst = feat_src
  
          if self._use_symmetric_norm:
              degs = graph.out_degrees().float().clamp(min=1)
              norm = torch.pow(degs, -0.5)
              shp = norm.shape + (1,) * (feat_src.dim() - 1)
              norm = torch.reshape(norm, shp)
              feat_src = feat_src * norm
  
          # NOTE: GAT paper uses "first concatenation then linear projection"
          # to compute attention scores, while ours is "first projection then
          # addition", the two approaches are mathematically equivalent:
          # We decompose the weight vector a mentioned in the paper into
          # [a_l || a_r], then
          # a^T [Wh_i || Wh_j] = a_l Wh_i + a_r Wh_j
          # Our implementation is much efficient because we do not need to
          # save [Wh_i || Wh_j] on edges, which is not memory-efficient. Plus,
          # addition could be optimized with DGL's built-in function u_add_v,
          # which further speeds up computation and saves memory footprint.
          el = (feat_src * self.attn_l).sum(dim=-1).unsqueeze(-1)
          graph.srcdata.update({"ft": feat_src, "el": el})
          # compute edge attention, el and er are a_l Wh_i and a_r Wh_j respectively.
          if self.attn_r is not None:
              er = (feat_dst * self.attn_r).sum(dim=-1).unsqueeze(-1)
              graph.dstdata.update({"er": er})
              graph.apply_edges(fn.u_add_v("el", "er", "e"))
          else:
              graph.apply_edges(fn.copy_u("el", "e"))
          e = self.leaky_relu(graph.edata.pop("e"))
  
          if self.training and self.edge_drop > 0:
              perm = torch.randperm(graph.num_edges(), device=e.device)
              bound = int(graph.num_edges() * self.edge_drop)
              eids = perm[bound:]
              graph.edata["a"] = torch.zeros_like(e)
              graph.edata["a"][eids] = self.attn_drop(
                  edge_softmax(graph, e[eids], eids=eids)
              )
          else:
              graph.edata["a"] = self.attn_drop(edge_softmax(graph, e))
  
          # message passing
          graph.update_all(
              fn.u_mul_e("ft", "a", "m"), fn.sum("m", "ft")
          )
          rst = graph.dstdata["ft"]
  
          if self._use_symmetric_norm:
              degs = graph.in_degrees().float().clamp(min=1)
              norm = torch.pow(degs, 0.5)
              shp = norm.shape + (1,) * (feat_dst.dim() - 1)
              norm = torch.reshape(norm, shp)
              rst = rst * norm
  
          # residual
          if self.res_fc is not None:
              resval = self.res_fc(h_dst).view(
                  h_dst.shape[0], -1, self._out_feats
              )
              rst = rst + resval
  
          # activation
          if self._activation is not None:
              rst = self._activation(rst)
  
          return rst
  
imports: import dgl.nn.pytorch
  import torch
  import torch.nn
  from dgl import function
  from dgl.ops import edge_softmax
  from dgl.utils import expand_as_pair
label_desc: Use image node_id 4 for calling the GATConv obj's underlying member method code with example usage: obj.forward(graph, feat) and returns: rst