And here’s a reply to your first question:
I think this is what is happening under the hood.
x.data has a pointer to data somewhere (via x.data.data_ptr() or x.data.storage()). Let’s call that data S for storage.
When you do prev_x = x.data, prev_x is being created with a pointer to S as well.
So both prev_x and x.data use S as their storage under the hood.
When you modify x.data in-place via x.data.add_(...), it updates S, so you see the changes in prev_x.
When you modify x.data = x.data + 1, you’re creating a new storage S' for x.data.
The reason behind this is that this is equivalent to x.data = x.data.add(1).
x.data.add(1) creates a tensor with a new storage S' (because it is not in-place), which is then being assigned to x.data.
Meanwhile x_prev still uses S as the underlying storage.