docstr: Conduct layer-wise inference to get all the node embeddings.
docstr_tok: ["Conduct","layer-wise","inference","to","get","all","the","node","embeddings","."]
code: def inference(
      self, g, device, batch_size, fused_sampling: bool = True
  ):
      """Conduct layer-wise inference to get all the node embeddings."""
      feat = g.ndata["feat"]
      #####################################################################
      # (HIGHLIGHT) Creating a MultiLayerFullNeighborSampler instance.
      # This sampler is used in the Graph Neural Networks (GNN) training
      # process to provide neighbor sampling, which is crucial for
      # efficient training of GNN on large graphs.
      #
      # The first argument '1' indicates the number of layers for
      # the neighbor sampling. In this case, it's set to 1, meaning
      # only the direct neighbors of each node will be included in the
      # sampling.
      #
      # The 'prefetch_node_feats' parameter specifies the node features
      # that need to be pre-fetched during sampling. In this case, the
      # feature named 'feat' will be pre-fetched.
      #
      # `prefetch` in DGL initiates data fetching operations in parallel
      # with model computations. This ensures data is ready when the
      # computation needs it, thereby eliminating waiting times between
      # fetching and computing steps and reducing the I/O overhead during
      # the training process.
      #
      # The difference between whether to use prefetch or not is shown:
      #
      # Without Prefetch:
      # Fetch1 ──> Compute1 ──> Fetch2 ──> Compute2 ──> Fetch3 ──> Compute3
      #
      # With Prefetch:
      # Fetch1 ──> Fetch2 ──> Fetch3
      #    │          │          │
      #    └─Compute1 └─Compute2 └─Compute3
      #####################################################################
      sampler = MultiLayerFullNeighborSampler(
          1, prefetch_node_feats=["feat"], fused=fused_sampling
      )
  
      dataloader = DataLoader(
          g,
          torch.arange(g.num_nodes()).to(g.device),
          sampler,
          device=device,
          batch_size=batch_size,
          shuffle=False,
          drop_last=False,
          num_workers=0,
      )
      buffer_device = torch.device("cpu")
      # Enable pin_memory for faster CPU to GPU data transfer if the
      # model is running on a GPU.
      pin_memory = buffer_device != device
  
      for layer_idx, layer in enumerate(self.layers):
          is_last_layer = layer_idx == len(self.layers) - 1
          y = torch.empty(
              g.num_nodes(),
              self.out_size if is_last_layer else self.hidden_size,
              device=buffer_device,
              pin_memory=pin_memory,
          )
          feat = feat.to(device)
          for input_nodes, output_nodes, blocks in tqdm.tqdm(
              dataloader
          ):
              x = feat[input_nodes]
              hidden_x = layer(blocks[0], x)  # len(blocks) = 1
              if layer_idx != len(self.layers) - 1:
                  hidden_x = F.relu(hidden_x)
                  hidden_x = self.dropout(hidden_x)
              # By design, our output nodes are contiguous.
              y[output_nodes[0] : output_nodes[-1] + 1] = hidden_x.to(
                  buffer_device
              )
          feat = y
      return y
  
file_path: dgl/examples/sampling/node_classification.py
imports: import argparse
  import time
  import dgl
  import dgl.nn
  import torch
  import torch.nn
  import torch.nn.functional
  import torchmetrics.functional
  import tqdm
  from dgl.data import AsNodePredDataset
  from dgl.dataloading import DataLoader, MultiLayerFullNeighborSampler, NeighborSampler
  from ogb.nodeproppred import DglNodePropPredDataset
label_desc: Use image node_id 3 for calling the SAGE obj's underlying member method code with example usage: obj.inference(g, device, batch_size, fused_sampling) and returns: y