According to the docs, the shape of x_birnn_h is (num_layers * num_directions, batch, hidden_size). A common way to to work with the last hidden states is:
output, hidden = self._birnn(x)
# hidden.shape = (n_layers * n_directions, batch_size, hidden_dim)
hidden = hidden.view(n_layers, n_directions, batch_size, hidden_dim)
# This view() comes directly from the PyTorch docs
# hidden.shape = (n_layers, n_directions, batch_size, hidden_dim)
hidden = hidden[-1]
# hidden.shape = (n_directions, batch_size, hidden_dim)
hidden_forward, hidden_backward = hidden[0], hidden[1]
# Both shapes (batch_size, hidden_dim)
hidden = torch.cat((hidden_forward, hidden_backward), dim=1)

Edit: If you want the last hidden have the same shape as the unidirectional GRU, you can also add hidden_forward and hidden_backward (instead of concatinating). I’ve seen it done, but don’t really know what’s the best practice here.