def forward(
        self, minibatch, task_idx, return_hidden=False, paired_trg=None
    ):
        """Propogate input through the network.

        Seq2Seq:
        inputs: minibatch['input_src'], minibatch['input_trg']
        input_src       - batch size x source sequence length
        input_trg       - batch size x target sequence length
        src_lengths     - batch size (list)
        paired_trg      - batch size x target sequence length or None
        returns: decoder_logit (pre-softmax over words)
        decoder_logit   - batch size x target sequence length x target vocab size

        NLI:
        sent1           - batch size x source sequence length
        sent2           - batch size x target sequence length
        sent1_lengths   - batch size (list)
        sent2_lengths   - batch size (list)
        rev_sent1       - batch size (LongTensor)
        rev_sent2       - batch size (LongTensor)
        returns: class_logits (pre-softmax over NLI classes)
        decoder_logit   - batch size x 3
        """
        if minibatch["type"] == "nli":
            sent1_emb = self.src_embedding(minibatch["sent1"])
            sent2_emb = self.src_embedding(minibatch["sent2"])

            sent1_lengths = minibatch["sent1_lens"].data.view(-1).tolist()
            sent1_emb = pack_padded_sequence(
                sent1_emb, sent1_lengths, batch_first=True
            )
            sent1, sent1_h = self.encoder(sent1_emb)

            sent2_lengths = minibatch["sent2_lens"].data.view(-1).tolist()
            sent2_emb = pack_padded_sequence(
                sent2_emb, sent2_lengths, batch_first=True
            )
            sent2, sent2_h = self.encoder(sent2_emb)

            if self.bidirectional:
                sent1_h = torch.cat((sent1_h[-1], sent1_h[-2]), 1)
                sent2_h = torch.cat((sent2_h[-1], sent2_h[-2]), 1)
            else:
                sent1_h = sent1_h[-1]
                sent2_h = sent2_h[-1]

            sent1_h = sent1_h.index_select(0, minibatch["rev_sent1"])
            sent2_h = sent2_h.index_select(0, minibatch["rev_sent2"])

            features = torch.cat(
                (
                    sent1_h,
                    sent2_h,
                    torch.abs(sent1_h - sent2_h),
                    sent1_h * sent2_h,
                ),
                1,
            )

            if return_hidden:
                return sent1_h, sent2_h, self.nli_decoder(features)
            else:
                return self.nli_decoder(features)

        else:
            src_emb = self.src_embedding(minibatch["input_src"])
            trg_emb = self.trg_embedding[task_idx](minibatch["input_trg"])
            src_lengths = minibatch["src_lens"].data.view(-1).tolist()
            src_emb = pack_padded_sequence(
                src_emb, src_lengths, batch_first=True
            )

            _, src_h_t = self.encoder(src_emb)

            if self.bidirectional:
                h_t = torch.cat((src_h_t[-1], src_h_t[-2]), 1)
            else:
                h_t = src_h_t[-1]

            h_t = h_t.unsqueeze(0)
            h_t = self.enc_drp(h_t)

            # Debug with squeeze on error.
            trg_h, _ = self.decoders[task_idx](
                trg_emb,
                h_t.view(-1, self.trg_hidden_dim),
                h_t.view(-1, self.trg_hidden_dim),
            )

            trg_h_reshape = trg_h.contiguous().view(
                trg_h.size(0) * trg_h.size(1), trg_h.size(2)
            )

            decoder_logit = self.decoder2vocab[task_idx](trg_h_reshape)
            decoder_logit = decoder_logit.view(
                trg_h.size(0), trg_h.size(1), decoder_logit.size(1)
            )

            if (
                self.paired_tasks is not None
                and task_idx in self.paired_tasks
                and paired_trg is not None
            ):
                other_task_idx = self.paired_tasks[task_idx]
                trg_emb_2 = self.trg_embedding[other_task_idx](paired_trg)

                trg_h_2, _ = self.decoders[other_task_idx](
                    trg_emb_2, h_t.squeeze(), h_t.squeeze()
                )

                trg_h_reshape_2 = trg_h_2.contiguous().view(
                    trg_h_2.size(0) * trg_h_2.size(1), trg_h_2.size(2)
                )

                decoder_logit_2 = self.decoder2vocab[other_task_idx](
                    trg_h_reshape_2
                )
                decoder_logit_2 = decoder_logit_2.view(
                    trg_h_2.size(0), trg_h_2.size(1), decoder_logit_2.size(1)
                )
                if return_hidden:
                    return decoder_logit, decoder_logit_2, h_t
                else:
                    return decoder_logit, decoder_logit_2

            if return_hidden:
                return decoder_logit, h_t
            else:
                return decoder_logit
Propogate input through the network.

        Seq2Seq:
        inputs: minibatch['input_src'], minibatch['input_trg']
        input_src       - batch size x source sequence length
        input_trg       - batch size x target sequence length
        src_lengths     - batch size (list)
        paired_trg      - batch size x target sequence length or None
        returns: decoder_logit (pre-softmax over words)
        decoder_logit   - batch size x target sequence length x target vocab size

        NLI:
        sent1           - batch size x source sequence length
        sent2           - batch size x target sequence length
        sent1_lengths   - batch size (list)
        sent2_lengths   - batch size (list)
        rev_sent1       - batch size (LongTensor)
        rev_sent2       - batch size (LongTensor)
        returns: class_logits (pre-softmax over NLI classes)
        decoder_logit   - batch size x 3
        
utils_nlp/models/gensen/multi_task_model.py