Skip to content

Commit 6eebb99

Browse files
authored
Fix SparseDenseMultiply gradient when the dense input is a scalar
1 parent 2265a07 commit 6eebb99

2 files changed

Lines changed: 22 additions & 1 deletion

File tree

pytensor/sparse/math.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,14 @@ def pullback(self, inputs, outputs, gout):
786786
(gz,) = gout
787787
assert psb._is_sparse_variable(x) and psb._is_dense_variable(y)
788788
assert psb._is_sparse_variable(gz)
789-
return y * gz, psb.dense_from_sparse(x * gz)
789+
if y.type.ndim == 0:
790+
# y was broadcast against every entry of x, so its gradient is the
791+
# sum of the contributions of all entries. Reduce on the sparse
792+
# product directly instead of densifying it first.
793+
gy = sp_sum(x * gz, sparse_grad=True)
794+
else:
795+
gy = psb.dense_from_sparse(x * gz)
796+
return y * gz, gy
790797

791798
def infer_shape(self, node, shapes):
792799
return [shapes[0]]

tests/sparse/test_math.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,20 @@ def test_MulDS(self):
100100
np.array([[1.0, 2], [3, 0], [0, 6]]),
101101
)
102102

103+
@pytest.mark.parametrize("format", ["csc", "csr"])
104+
def test_MulSD_scalar_grad(self, format):
105+
# Multiplying a sparse matrix by a scalar broadcasts the scalar over
106+
# every entry, so the gradient wrt the scalar must be a scalar too.
107+
array = np.array([[1.0, 0], [3, 0], [0, 6]])
108+
x = as_sparse_variable(as_sparse_format(array, format))
109+
y = scalar("y", dtype="float64")
110+
111+
gy = pytensor.grad(psm.sp_sum(multiply(x, y)), y)
112+
assert gy.type.ndim == 0
113+
114+
f = pytensor.function([y], gy)
115+
utt.assert_allclose(array.sum(), f(2.0))
116+
103117
def _testSS(self, op, array1=None, array2=None):
104118
if array1 is None:
105119
array1 = np.array([[1.0, 0], [3, 0], [0, 6]])

0 commit comments

Comments
 (0)