Below is a graph-based parser implemented in pytorch, refer to Simple and Accurate Dependency Parsing Using Bidirectional LSTM Feature Representations for detail.
class Parser(torch.nn.Module):
    def __init__(self,
                 word_vocab_size,
                 word_emb_size,
                 pos_vocab_size,
                 pos_emb_size,
                 hidden_size,
                 pre_trained=None,
                 num_layers=1):
        super(Parser, self).__init__()

        self.num_layers = num_layers
        self.hidden_size = hidden_size

        self.word_embedding = torch.nn.Embedding(word_vocab_size, word_emb_size)
        self.pos_embedding = torch.nn.Embedding(pos_vocab_size, pos_emb_size)

        if pre_trained is not None:
            pre_word_embedding = np.random.uniform(-0.01, 0.01,size=[word_vocab_size, word_emb_size])
            wordvec_file = open(pre_trained)
            wiki_words = {}
            for line in wordvec_file.readlines():
                split = line.split(' ')
                word = split[0]
                emb = split[1:-1]
                wiki_words[word] = emb
            for word in word_dict:
                if word in wiki_words:
                    pre_word_embedding[word_dict[word]] = \
                        np.array(list(map(float, wiki_words[word])))
            self.word_embedding.weight.data.copy_(torch.from_numpy(pre_word_embedding))
            logging.info('Pre-trained embedding loaded.')

        self.gru = torch.nn.GRU(input_size=word_emb_size + pos_emb_size,
                                hidden_size=hidden_size,
                                # dropout=0.5,
                                bidirectional=True)

        self.score = torch.nn.Sequential(
            torch.nn.Linear(2 * 2 * hidden_size, 2 * hidden_size),
            torch.nn.ReLU(),
            torch.nn.Linear(2 * hidden_size, 1)
            # torch.nn.Linear(2 * hidden_size, hidden_size),
            # torch.nn.ReLU(),
            # torch.nn.Linear(hidden_size, 1),
        )


    def forward(self, word_indices, postag_indices, seq_lens):
        # shape is [batch * max_length]
        word_indices = var(torch.LongTensor(word_indices))
        postag_indices = var(torch.LongTensor(postag_indices))
        batch_size = word_indices.size(0)
        word_embs = self.word_embedding(word_indices)
        postag_embs = self.pos_embedding(postag_indices)
        joint_embs = torch.cat([word_embs, postag_embs], dim=2)
        joint_embs = joint_embs.t()
        joint_embs = pack_padded_sequence(joint_embs, seq_lens, batch_first=False)

        h0 = var(torch.zeros(self.num_layers * 2,
                             batch_size,
                             self.hidden_size))
        gru_word, _ = self.gru(joint_embs, h0)

        gru_word = pad_packed_sequence(gru_word)[0]
        gru_word = gru_word.t()

        scores = []
        D = self.hidden_size * 2
        for b in range(batch_size):
            L = seq_lens[b]
            # [[1,2,3],[4,5,6]] -> [[[1,2,3],[4,5,6]], [[1,2,3],[4,5,6]]]
            expanded = gru_word[b][0:L].expand(L, L, D)
            # [[1,2,3],[1,2,3],[4,5,6],[4,5,6]]
            foo = torch.transpose(expanded, 0, 1).contiguous().view(-1, D)
            # [[1,2,3],[4,5,6],[1,2,3],[4,5,6]]
            bar = expanded.contiguous().view(-1, D)
            foobar = torch.cat([foo, bar], dim=1)
            b_score = self.score(foobar)
            b_score = b_score.view(L, L)
            b_score = F.softmax(b_score)
            scores.append(b_score)

        return scores


parser = gpu(Parser(word_vocab_size=len(word_dict),
                    word_emb_size=WORD_EMB_SIZE,
                    pos_vocab_size=len(pos_dict),
                    pos_emb_size=POS_EMB_SIZE,
                    hidden_size=HIDDEN_SIZE,
                    pre_trained='../word2vec/english_skip_300.txt',
                    num_layers=1))

opt = torch.optim.Adam(params=parser.parameters())

for epoch in range(MAX_EPOCH):
   ……