'Inputs:
x1 = document word indices             [batch * len_d]
x1_f = document word features indices  [batch * len_d * nfeat]
x1_mask = document padding mask        [batch * len_d]
x2 = question word indices             [batch * len_q]
x2_mask = question padding mask        [batch * len_q]'

def forward(self, x1, x1_f, x1_mask, x2, x2_mask):

    x1_emb = self.embedding(x1)
    x2_emb = self.embedding(x2)
    if (self.opt['dropout_emb'] > 0):
        x1_emb = nn.functional.dropout(x1_emb, p=self.opt['dropout_emb'], training=self.training)
        x2_emb = nn.functional.dropout(x2_emb, p=self.opt['dropout_emb'], training=self.training)
    if self.opt['use_qemb']:
        x2_weighted_emb = self.qemb_match(x1_emb, x2_emb, x2_mask)
        drnn_input = torch.cat([x1_emb, x2_weighted_emb, x1_f], 2)
    else:
        drnn_input = torch.cat([x1_emb, x1_f], 2)
    doc_hiddens = self.doc_rnn(drnn_input, x1_mask)
    question_hiddens = self.question_rnn(x2_emb, x2_mask)
    if (self.opt['question_merge'] == 'avg'):
        q_merge_weights = layers.uniform_weights(question_hiddens, x2_mask)
    elif (self.opt['question_merge'] == 'self_attn'):
        q_merge_weights = self.self_attn(question_hiddens, x2_mask)
    question_hidden = layers.weighted_avg(question_hiddens, q_merge_weights)
    start_scores = self.start_attn(doc_hiddens, question_hidden, x1_mask)
    end_scores = self.end_attn(doc_hiddens, question_hidden, x1_mask)
    return (start_scores, end_scores)
