The work flow you’ve described would be in my opinion suitable for a high-level API like Ignite 6.
The reason this isn’t the standard in plain PyTorch is that you have more flexibility using all modules separately.
E.g. if you would like to create an artificially larger batch size because you are running our of memory, you can just execute multiple backward calls, accumulating the gradients, and finally call optimizer.step().