If you use reduction='sum' in your criterion, the gradients should be the same:
model = nn.Linear(10, 2)
criterion = nn.NLLLoss(reduction='sum')

x = torch.randn(2, 10)
y = torch.empty(2, dtype=torch.long).random_(2)

# use whole batch
loss = criterion(model(x), y)
loss.backward()
model_grad_batch = model.weight.grad.clone()
print(model_grad_batch)

# use separate samples
model.zero_grad()

loss = 0
for idx in range(x.size(0)):
    loss += criterion(model(x[idx].unsqueeze(0)), y[idx].unsqueeze(0))
loss.backward()
model_grad_sep = model.weight.grad.clone()
print(model_grad_sep)

torch.allclose(model_grad_batch, model_grad_sep)

Some layers like nn.BatchNorm will behave differently, so maybe you could try to tune the momentum etc.