It’s telling you the problem right here 
RuntimeError: Expected hidden size (1, 1, 512), got (2, 1, 512)
these hidden states are (num_layers * num_directions, batch_size, hidden_size) so when you turn on the bi-directional flag it doubles the first dim. You can either just take the last layer (or num decoder layers) on the first dim like i did in my helper classes
decoder_hidden = encoder_hidden[-decoder.n_layers:]
source: https://github.com/A-Jacobson/minimal-nmt/blob/master/decoding_helpers.py 62
or you could reshape it to (1, 1, 1024) and sum across the last dimension like we did with the encoder output.
The second option is probably strictly more correct as you’d get the hidden state for both directions but the first option works fine.
Your model may work without this part but you shouldn’t have to initialize the encoder hidden state and you should be able to feed batches of full sequences to the encoder (this will be much much faster than feeding one item at a time like your code). Also, the encoder and decoder can share the same optimizer.