Yes, you are right. What I am say is that In the pytorch official example, usually there will be some code like this:
for epoch in range(args.start_epoch, args.epochs):
        if args.distributed:
            train_sampler.set_epoch(epoch)
        adjust_learning_rate(optimizer, epoch)

        # train for one epoch
        train(train_loader, model, criterion, optimizer, epoch)

        # evaluate on validation set
        prec1 = validate(val_loader, model, criterion)

from imagenet.py 6.
From the code above, usually pytorch update lr out of the loop over train_loader, but caffe update every iter. I can also change the code adjust_learning_rate(optimizer, epoch) to  adjust_learning_rate(optimizer, iter) and move it into the loop over train_loader too. But in many example code, I found that write in the former style.