For the training part:

Try using optim.Adam instead of optim.SGD.
Add a nn.dropout (dropout = 0.1) layer before Linear layer in your model.
Assuming you have sufficient high-quality data, make sure training is happening correctly (loss values decrease and converge). Plot the loss values.

For the inference part:

Simply pass the input to model and simply use np.argmax to pick the next best char. You don’t need to use the (.) operators functions for embeddings etc.