Skip to content

Commit 8dc3bf1

Browse files
bruAristimunhalucascolley
authored andcommitted
Preserve torch autograd in the batched cov path
The generic `cov` implementation called `xp.asarray(m)` on its input. For torch this detaches gradients and mutates the caller's tensor in place, so `cov` on a batched tensor (ndim > 2, which routes to the generic path) with `requires_grad=True` returned a detached result and silently zeroed the input's grad. The call is unnecessary: the delegation layer already guarantees `m` is an array (it calls `array_namespace(m)` and reads `m.ndim`). Drop it, and add a torch autograd regression test.
1 parent 8d1989c commit 8dc3bf1

2 files changed

Lines changed: 19 additions & 1 deletion

File tree

src/array_api_extra/_lib/_funcs.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,9 @@ def cov(
270270
xp: ArrayNamespace,
271271
) -> Array: # numpydoc ignore=PR01,RT01
272272
"""See docstring in array_api_extra._delegation."""
273-
m = xp.asarray(m)
273+
# NB: no `xp.asarray(m)` here. The delegation layer already guarantees `m`
274+
# is an array (it calls `array_namespace(m)` and reads `m.ndim`), and on
275+
# torch `xp.asarray` detaches gradients and mutates the caller's tensor.
274276
dtype = (
275277
xp.float64 if xp.isdtype(m.dtype, "integral") else xp.result_type(m, xp.float64)
276278
)

tests/test_funcs.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,22 @@ def test_weights_wrong_length(self, xp: ModuleType):
801801
with pytest.raises((ValueError, RuntimeError)):
802802
_ = cov(m, correction=0.5, aweights=w_bad)
803803

804+
def test_torch_autograd(self, torch: ModuleType):
805+
# The batched (generic) path must not detach gradients or mutate the
806+
# input tensor in place, as `xp.asarray` does on torch.
807+
xp = torch
808+
rng = np.random.default_rng(20260417)
809+
m = xp.asarray(rng.random((4, 3, 20)), dtype=xp.float64)
810+
m.requires_grad_(True)
811+
# cov returns the array-api `Array` type; at runtime it is a torch
812+
# tensor, so cast to access autograd attributes without type errors.
813+
c = cast(Any, cov(m)) # batched -> generic path
814+
assert c.requires_grad
815+
assert m.requires_grad # input tensor not mutated
816+
c.sum().backward()
817+
assert m.grad is not None
818+
assert bool(xp.all(xp.isfinite(m.grad)))
819+
804820

805821
@pytest.mark.xfail_xp_backend(Backend.SPARSE, reason="no arange", strict=False)
806822
class TestOneHot:

0 commit comments

Comments
 (0)