Thanks for the reply! I think the confusion comes from examples I’ve looked at like the code snippet below. Note in it the line out = torch.argmax(outputs.detach(),dim=1) used during training. I was confused why this was used here but not in the training loop I referenced in the pytorch tutorial (wasn’t sure if I wasn’t understanding something). Similarly note at the bottom of this training block the line out = model(inputs.cuda()).cpu(), which is done during validation. Any thoughts on why these steps are required in this training block as opposed to the one on pytorch’s site?
for epoch in range(5):  
model.train(True)
running_loss = 0.0
running_acc = 0
for i, data in enumerate(trainloader, 0):
    inputs, labels = data
    inputs, labels = inputs.cuda(),labels.cuda()

    optimizer.zero_grad()
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    loss.backward()
    optimizer.step()

    # print statistics
    running_loss += loss.item() * inputs.size(0)
    out = torch.argmax(outputs.detach(),dim=1)
    assert out.shape==labels.shape
    running_acc += (labels==out).sum().item()
print(f"Train loss {epoch+1}: {running_loss/len(trainset)},Train Acc:{running_acc*100/len(trainset)}%")

correct = 0
model.train(False)
with torch.no_grad():
    for inputs,labels in valloader:
        out = model(inputs.cuda()).cpu()
        out = torch.argmax(out,dim=1)
        acc = (out==labels).sum().item()
        correct += acc
print(f"Val accuracy:{correct*100/len(valset)}%")
if correct>best_val_acc:
    best_val_acc = correct
    best_val_model = deepcopy(model.state_dict())
lr_scheduler.step()