why do you define it as autograd function? The way you used it, you always create a new instance of your Function. Why not defining it like this:
class DiceCoeff(torch.nn.Module):
    def __init__(self, eps=0.0001)
        super().__init__()
        self._eps = eps

    def forward(self, prediction, target):
        inter = torch.dot(prediction.view(-1), target.view(-1)) + self._eps
        union = prediction.sum() + target.sum() + self._eps

        return 2*inter.float()/union.float()

and using it like this:
dice_crit = DiceCoeff()
total_dice_coeff = 0
single_dices = [dice_crit(_x, _y) for _x, _y in zip(prediction, target)]

for _dice in single_dices:
    total_dice_coeff += _dice

total_dice_coeff = total_dice_coeff / len(single_dices)

This way you only create the criterion once, autograd handles the gradient computing (in a very efficient way) and you don’t have to create the s tensor yourself every time. From my expierience custom loss functions are almost as fast as the native ones, if they are implemented in torch.nn.Module
Can you test if this approach speeds up your training?
EDIT: Assuming prediction and target are minibatch-tensors you could do something like this:
class DiceCoeff(torch.nn.Module):
    def __init__(self, eps=0.0001)
        super().__init__()
        self._eps = eps

    def forward(self, prediction, target):
        flattened_pred = prediction.view(prediction.size(0), -1)
        flattened_target = target.view(target.size(0), -1)
        inter = torch.dot(flattened_pred, flattened_target) + self._eps
        union = flattened_pred.sum(-1) + flattened_target.sum(-1) + self._eps

        return (2*inter.float()/union.float()).mean()

and simply pass the whole batches like this:
dice_crit = DiceCoeff()
total_dice_coeff = dice_crit(predictions, targets)

and you will directly get the mean dice for the current batch