Skip to content

Commit 756da08

Browse files
Fix edge-mode pad gradient by pinning the edge width to 1
_get_edges sliced with symbolic indices, leaving the length-1 axis unknown, so set_subtensor's gradient never summed over the broadcast.
1 parent 3bb9e9c commit 756da08

2 files changed

Lines changed: 11 additions & 26 deletions

File tree

pytensor/tensor/pad.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,13 @@ def _get_edges(
8383
right_slice = slice_at_axis(slice(right_index - 1, right_index), axis)
8484
right_edge = padded[right_slice]
8585

86-
return left_edge, right_edge
86+
# The slices are symbolic, so `axis` comes back with an unknown length. Callers
87+
# broadcast these edges across the pad area, and the gradient of that
88+
# broadcast is only summed when the length is known to be 1.
89+
return (
90+
specify_broadcastable(left_edge, axis),
91+
specify_broadcastable(right_edge, axis),
92+
)
8793

8894

8995
def _symbolic_pad(

tests/tensor/test_pad.py

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -316,23 +316,7 @@ def test_pad_grad_has_static_slice_bounds(mode):
316316
assert _dynamic_subtensors([x], grad_x) == []
317317

318318

319-
@pytest.mark.parametrize(
320-
"mode",
321-
[
322-
pytest.param(
323-
m,
324-
marks=(
325-
pytest.mark.xfail(
326-
reason="edge gradient broadcasts the border slice against the "
327-
"wrong width, so it fails on every backend"
328-
)
329-
if m == "edge"
330-
else ()
331-
),
332-
)
333-
for m in ALL_MODES
334-
],
335-
)
319+
@pytest.mark.parametrize("mode", ALL_MODES)
336320
def test_pad_grad_jax(mode):
337321
"""The gradient of every pad mode should compile and run under jax.jit."""
338322
pytest.importorskip("jax")
@@ -413,18 +397,13 @@ def test_pad_rejects_non_integral_pad_width(mode):
413397
pad(x, (1.5, 1.5), mode=mode, **MODE_KWARGS.get(mode, {}))
414398

415399

416-
@pytest.mark.xfail(
417-
reason="edge gradient broadcasts the border slice against the wrong width"
418-
)
419400
@pytest.mark.parametrize(
420401
"pad_width",
421-
[((1, 1), (2, 2)), ((0, 3), (2, 1)), 2],
422-
ids=["axes", "sides", "scalar"],
402+
[((1, 1), (1, 1)), ((1, 1), (2, 2)), ((0, 3), (2, 1)), 2, ((5, 5), (5, 5))],
403+
ids=["all_ones", "axes", "sides", "scalar", "wider_than_axis"],
423404
)
424405
def test_edge_pad_grad(pad_width):
425-
"""``edge`` gradients fail for any pad width other than 1 on every side.
426-
427-
Each input element is copied a fixed number of times into the output, so
406+
"""Each input element is copied a fixed number of times into the output, so
428407
``d(sum(pad(x)))/dx`` is exactly that copy count.
429408
"""
430409
x = pt.tensor("x", shape=(8, 8))

0 commit comments

Comments
 (0)