itaymr:

As I understood it, logits are the raw predictions that are fed into the softmax function which produces the probabilities for each class - if so, why is the final output of the model logits?


You can pick between these approaches:

model output as raw logits + nn.CrossEntropyLoss

model output as log probabilities (via nn.LogSoftmax or F.log_sotmax) + nn.NLLLoss

numerically unstable approach by applying torch.log on probabilities (via torch.log(F.softmax(output, dim=1)))

Here is a code snippet for the valid approaches (I skipped the numerically unstable approach, as you shouldn’t use it):
# setup
batch_size = 2
nb_classes = 10
output = torch.randn(batch_size, nb_classes) # initialize as logits
target = torch.randint(0, nb_classes, (batch_size,))

# nn.CrossEntropyLoss with logits
criterion = nn.CrossEntropyLoss()
# pass raw logits to the criterion
loss_ce = criterion(output, target)

# nn.NLLLoss with log probabilities
criterion = nn.NLLLoss()
log_probs = F.log_softmax(output, dim=1)
loss_nll = criterion(log_probs, target)

# same but with functional API, which is applied in `nn.CrossEntropyLoss` internally
loss_nll_func = F.nll_loss(F.log_softmax(output, dim=1), target)

# compare
print((loss_ce - loss_nll).abs().max())
> tensor(0.)

print((loss_ce - loss_nll_func).abs().max())
> tensor(0.)

As you can see, the different is zero since all approaches are using the same operations.
Here you can also see the code showing that nn.CrossEntropyLoss is using F.nll_loss(F.log_softmax(...)) internally (the names are difference as it’s written in the C++ backend:
    ret = at::nll_loss_nd(
        at::log_softmax(self, 1, self.scalar_type()),
        target,
        weight,
        reduction,
        ignore_index);




 itaymr:

When you say F.log_softmax is calculated internally, is that done automatically?


Yes, F.log_softmax is applied automatically in nn.CrossEntropyLoss on the model outputs.



 itaymr:

I would really appreciate it if you could give me a working example, following the docs  for a classifier, they did calculate the criterion & loss in the same fashion as I did while also using cross-entropy.


The linked example is correct as it returns the logits from the model (output of the last linear layer without any activation function applied on it) and passes them to nn.CrossEntropyLoss.
Your first example used nn.Softmax as the last activation function, which was wrong.