Hi Stas,
good to see you around! So drop the .data, wrap in no_grad works here as far as I can see:
def perfect_update(x):
    with torch.no_grad():
        x -= 0.0001 * x.grad
    x.grad.zero_()
    return x

(I’m having reservations about calling it update when it also zeros grad and about returning x, too, but hey.)
There are two things you previously achieved by using .data:

We want to have no autograd graph update for the subtraction. This we can get without data by wrapping the subtraction in with torch.no_grad(). It will preserve the annotation that x requires gradients.
The other thing that .data does it inhibits marking x as in-place updated. But it is in fact the right thing to have x  marked as modified. The stuff needing the old x will complain (rightfully, you have a bug in your code if you still need the old x when it is gone), everything else will happily take the new x.

The prerequisite for all this is that x is a leaf in the autograd graph, but that should better be the case anyway.
Work for dropping .data is tracked at #30987. Personally, I think one could just go ahead and tick off the things from the list that can be done today, but given that I’m not the one doing it…
Best regards
Thomas