a) Not quite. You are creating a single optimizer and are overwriting the object, so only the last model would be updated. Besides that you are currently storing the loss tensor in the losses list which will not only store the actual loss value but the entire computation graph and would thus increase the memory usage in each iteration. Call detach() or item() on the loss before appending it (same for predicted).
b) No, as described in a) you would have to create an optimizer for each model and also call optimizer.zero_grad() in the training loop.
c) If you are using the GPU, the CUDA kernels would be scheduled on the device and could execute in parallel if the CPU is fast enough to enqueue the kernels and if enough GPU resources are available.