No, these are great questions.
questions 1 & 2
In Python, there are several special methods which user-defined classes can override to allow certain kinds of operations on the class or instances.
They’re all surrounded by double underscores (so they’re called “dunder” methods):


__init__ is one of them, which defines the constructor;

__str__ is another one – what you implement there defines what Python will do if you call str(obj).
The __call__ dunder method defines what Python will do if you call
an instance of the class as if it were a function.

In PyTorch, the __call__ 3 method of nn.Module instances sets up user-defined hooks if they exist, then calls the instance’s forward method. So calling net(var) is the same thing as calling net.__call__(var), which will itself call net.forward(var) to perform the actual forward pass.
question 3
The same thing is happening here.
nn.CrossEntropyLoss is a class whose constructor takes weights and size_average, but instances of nn.CrossEntropyLoss, including criterion, define a forward method which is called from nn.Module's __call__ method.
The distinction between training and testing modes is actually implemented similarly to Keras, but in PyTorch it’s done with a pair of methods that change the state of the model: model.train() sets the model to training mode while model.eval() sets it to test mode.
Others questions:

Yes, that should work.
I think that means CIFAR doesn’t natively have a validation set? If so you can always split the train set further.
Yes, loss.data[0] has the average loss for the minibatch. You can just keep appending it to a list to keep track of the losses for the whole epoch; make sure you use loss.data rather than just loss because the temporary buffers for the graph won’t be freed if you keep around a bunch of loss variables.
Yes, you have to call .cuda() on your model and your input data. You shouldn’t have to call it anywhere else – if you’re creating Variables in your model’s forward pass, you should use expressions like Variable(existing_var.data.new(size).zero_()) to make the new variable created on the same device (CPU/GPU) as the existing variable.
The latter saves the entire model using Python’s pickling, which is a very precarious way to save complicated custom classes. Basically, it doesn’t actually save the model’s structure, just the names of the classes that built it, so changing your model’s code can lead to weird and unpredictable behavior of loaded pickles while with load_state_dict you know you’re only saving and loading the params.
Not a computer vision person; I don’t know.
If the site lets you add to the list, I think you should go ahead.