def fast_learn(self, batch_edges, neg_nodes=None):
        """Learn a batch of edges in a fast way. It has the following features:
            1. It calculating the gradients directly without the forward operation.
            2. It does sigmoid by a looking up table.

        Specifically, for each positive/negative node pair (i,j), the updating procedure is as following:
            score = self.fast_sigmoid(u_embedding[i].dot(v_embedding[j]))
            # label = 1 for positive samples; label = 0 for negative samples.
            u_embedding[i] += (label - score) * v_embedding[j]
            v_embedding[i] += (label - score) * u_embedding[j]

        Parameters
        ----------
        batch_edges list : a list of node sequnces
        neg_nodes torch.LongTensor : a long tensor of sampled true negative nodes. If neg_nodes is None,
            then do negative sampling randomly from the nodes in batch_walks as an alternative.

        Usage example
        -------------
        batch_walks = torch.LongTensor([[1,2], [3,4], [5,6]])
        neg_nodes = None
        """
        lr = self.lr

        # [batch_size, 2]
        nodes = batch_edges
        if self.only_gpu:
            nodes = nodes.to(self.device)
            if neg_nodes is not None:
                neg_nodes = neg_nodes.to(self.device)
        bs = len(nodes)

        if self.fst:
            emb_u = (
                self.fst_u_embeddings(nodes[:, 0])
                .view(-1, self.emb_dimension)
                .to(self.device)
            )
            emb_v = (
                self.fst_u_embeddings(nodes[:, 1])
                .view(-1, self.emb_dimension)
                .to(self.device)
            )

            ## Postive
            emb_pos_u, emb_pos_v = emb_u, emb_v
            grad_u_pos, grad_v_pos = self.fast_pos_bp(
                emb_pos_u, emb_pos_v, True
            )

            ## Negative
            emb_neg_u = emb_pos_u.repeat((self.negative, 1))

            if bs < self.batch_size:
                index_emb_negu, index_emb_negv = init_emb2neg_index(
                    self.negative, bs
                )
                index_emb_negu = index_emb_negu.to(self.device)
                index_emb_negv = index_emb_negv.to(self.device)
            else:
                index_emb_negu = self.index_emb_negu
                index_emb_negv = self.index_emb_negv

            if neg_nodes is None:
                emb_neg_v = torch.index_select(emb_v, 0, index_emb_negv)
            else:
                emb_neg_v = self.fst_u_embeddings.weight[neg_nodes].to(
                    self.device
                )

            grad_u_neg, grad_v_neg = self.fast_neg_bp(
                emb_neg_u, emb_neg_v, True
            )

            ## Update
            grad_u_pos.index_add_(0, index_emb_negu, grad_u_neg)
            grad_u = grad_u_pos
            if neg_nodes is None:
                grad_v_pos.index_add_(0, index_emb_negv, grad_v_neg)
                grad_v = grad_v_pos
            else:
                grad_v = grad_v_pos

            # use adam optimizer
            grad_u = adam(
                grad_u,
                self.fst_state_sum_u,
                nodes[:, 0],
                lr,
                self.device,
                self.only_gpu,
            )
            grad_v = adam(
                grad_v,
                self.fst_state_sum_u,
                nodes[:, 1],
                lr,
                self.device,
                self.only_gpu,
            )
            if neg_nodes is not None:
                grad_v_neg = adam(
                    grad_v_neg,
                    self.fst_state_sum_u,
                    neg_nodes,
                    lr,
                    self.device,
                    self.only_gpu,
                )

            if self.mixed_train:
                grad_u = grad_u.cpu()
                grad_v = grad_v.cpu()
                if neg_nodes is not None:
                    grad_v_neg = grad_v_neg.cpu()
                else:
                    grad_v_neg = None

                if self.async_update:
                    grad_u.share_memory_()
                    grad_v.share_memory_()
                    nodes.share_memory_()
                    if neg_nodes is not None:
                        neg_nodes.share_memory_()
                        grad_v_neg.share_memory_()
                    self.async_q.put(
                        (grad_u, grad_v, grad_v_neg, nodes, neg_nodes, True)
                    )

            if not self.async_update:
                self.fst_u_embeddings.weight.data.index_add_(
                    0, nodes[:, 0], grad_u
                )
                self.fst_u_embeddings.weight.data.index_add_(
                    0, nodes[:, 1], grad_v
                )
                if neg_nodes is not None:
                    self.fst_u_embeddings.weight.data.index_add_(
                        0, neg_nodes, grad_v_neg
                    )

        if self.snd:
            emb_u = (
                self.snd_u_embeddings(nodes[:, 0])
                .view(-1, self.emb_dimension)
                .to(self.device)
            )
            emb_v = (
                self.snd_v_embeddings(nodes[:, 1])
                .view(-1, self.emb_dimension)
                .to(self.device)
            )

            ## Postive
            emb_pos_u, emb_pos_v = emb_u, emb_v
            grad_u_pos, grad_v_pos = self.fast_pos_bp(
                emb_pos_u, emb_pos_v, False
            )

            ## Negative
            emb_neg_u = emb_pos_u.repeat((self.negative, 1))

            if bs < self.batch_size:
                index_emb_negu, index_emb_negv = init_emb2neg_index(
                    self.negative, bs
                )
                index_emb_negu = index_emb_negu.to(self.device)
                index_emb_negv = index_emb_negv.to(self.device)
            else:
                index_emb_negu = self.index_emb_negu
                index_emb_negv = self.index_emb_negv

            if neg_nodes is None:
                emb_neg_v = torch.index_select(emb_v, 0, index_emb_negv)
            else:
                emb_neg_v = self.snd_v_embeddings.weight[neg_nodes].to(
                    self.device
                )

            grad_u_neg, grad_v_neg = self.fast_neg_bp(
                emb_neg_u, emb_neg_v, False
            )

            ## Update
            grad_u_pos.index_add_(0, index_emb_negu, grad_u_neg)
            grad_u = grad_u_pos
            if neg_nodes is None:
                grad_v_pos.index_add_(0, index_emb_negv, grad_v_neg)
                grad_v = grad_v_pos
            else:
                grad_v = grad_v_pos

            # use adam optimizer
            grad_u = adam(
                grad_u,
                self.snd_state_sum_u,
                nodes[:, 0],
                lr,
                self.device,
                self.only_gpu,
            )
            grad_v = adam(
                grad_v,
                self.snd_state_sum_v,
                nodes[:, 1],
                lr,
                self.device,
                self.only_gpu,
            )
            if neg_nodes is not None:
                grad_v_neg = adam(
                    grad_v_neg,
                    self.snd_state_sum_v,
                    neg_nodes,
                    lr,
                    self.device,
                    self.only_gpu,
                )

            if self.mixed_train:
                grad_u = grad_u.cpu()
                grad_v = grad_v.cpu()
                if neg_nodes is not None:
                    grad_v_neg = grad_v_neg.cpu()
                else:
                    grad_v_neg = None

                if self.async_update:
                    grad_u.share_memory_()
                    grad_v.share_memory_()
                    nodes.share_memory_()
                    if neg_nodes is not None:
                        neg_nodes.share_memory_()
                        grad_v_neg.share_memory_()
                    self.async_q.put(
                        (grad_u, grad_v, grad_v_neg, nodes, neg_nodes, False)
                    )

            if not self.async_update:
                self.snd_u_embeddings.weight.data.index_add_(
                    0, nodes[:, 0], grad_u
                )
                self.snd_v_embeddings.weight.data.index_add_(
                    0, nodes[:, 1], grad_v
                )
                if neg_nodes is not None:
                    self.snd_v_embeddings.weight.data.index_add_(
                        0, neg_nodes, grad_v_neg
                    )

        return
Learn a batch of edges in a fast way. It has the following features:
            1. It calculating the gradients directly without the forward operation.
            2. It does sigmoid by a looking up table.

        Specifically, for each positive/negative node pair (i,j), the updating procedure is as following:
            score = self.fast_sigmoid(u_embedding[i].dot(v_embedding[j]))
            # label = 1 for positive samples; label = 0 for negative samples.
            u_embedding[i] += (label - score) * v_embedding[j]
            v_embedding[i] += (label - score) * u_embedding[j]

        Parameters
        ----------
        batch_edges list : a list of node sequnces
        neg_nodes torch.LongTensor : a long tensor of sampled true negative nodes. If neg_nodes is None,
            then do negative sampling randomly from the nodes in batch_walks as an alternative.

        Usage example
        -------------
        batch_walks = torch.LongTensor([[1,2], [3,4], [5,6]])
        neg_nodes = None
        
examples/pytorch/ogb/line/model.py