Thanks for the example.
Because you cast the tensor after setting requires_grad_ on it, this weird behavior might be happening.
Since your numpy array is already a np.float64, your DoubleTensor cast would probably be a no-op, thus masking the behavior.
In your second call using a FloatTensor your tensor gets a new grad_fn named CopyBackwards.
It’s still strange, that the grad is None, so this might still be a bug.
You could fix it currently by setting requires_grad_ after casting the tensor:
def foo_double(np_array):

    tensor = torch.from_numpy(np_array).type(torch.DoubleTensor)
    tensor.requires_grad_(True)
    squared_norm = torch.sum(tensor**2)
    squared_norm.backward()

    return tensor.grad


def foo_float(np_array):

    tensor = torch.from_numpy(np_array).type(torch.FloatTensor)
    tensor.requires_grad_(True)
    squared_norm = torch.sum(tensor**2)
    squared_norm.backward()

    return tensor.grad

@SimonW or @albanD could you please have a look at this?
It is a bug or is it wanted behavior?
A small example:
x = torch.randn(1, requires_grad=True, dtype=torch.double)
tensor = x.float()
squared_norm = torch.sum(tensor**2)
squared_norm.backward()
print(tensor.grad_fn)
> <CopyBackwards object at 0x7f06240ea290>
print(tensor.grad)
> None
print(x.grad)  
> tensor([-0.3383], dtype=torch.float64)