From 3b5ab51de55aa47f6ca691dda19cf5907f168a4c Mon Sep 17 00:00:00 2001 From: Roman Shapovalov Date: Tue, 14 Jul 2026 12:07:08 -0700 Subject: [PATCH] Fix backprop through cot_laplacian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: `_cot_laplacian_python` used in-place tensor operations (`clamp_`, `/=`, `+=`) on intermediates that participate in autograd. Once the resulting sparse Laplacian is used in a backward pass, these in-place mutations raise a runtime error: ``` RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation: [torch.sparse.FloatTensor [4, 4]], which is output 0 of SparseCooTensorWithDimsAndTensors, is at version 1; expected version 0 instead. Hint: enable anomaly detection to find the operation that failed to compute its gradient, with torch.autograd.set_detect_anomaly(True, check_nan=False). ``` Replace the in-place ops with out-of-place equivalents so gradients can flow back to the input vertices. - `clamp_` → `clamp` - `cot /= 4.0` → `cot = cot / 4.0` - `L += L.t()` → `L = L + L.t()` Reviewed By: bottler Differential Revision: D111896032 fbshipit-source-id: 4c36677487bae5d37a9d81cb791400576f4b2c5f --- pytorch3d/ops/laplacian_matrices.py | 8 ++++---- tests/test_laplacian_matrices.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pytorch3d/ops/laplacian_matrices.py b/pytorch3d/ops/laplacian_matrices.py index 9227ba5d..a2c2997b 100644 --- a/pytorch3d/ops/laplacian_matrices.py +++ b/pytorch3d/ops/laplacian_matrices.py @@ -103,8 +103,8 @@ def cot_laplacian( s = 0.5 * (A + B + C) # note that the area can be negative (close to 0) causing nans after sqrt() # we clip it to a small positive value - # pyre-fixme[16]: `float` has no attribute `clamp_`. - area = (s * (s - A) * (s - B) * (s - C)).clamp_(min=eps).sqrt() + # pyre-fixme[16]: `float` has no attribute `clamp`. + area = (s * (s - A) * (s - B) * (s - C)).clamp(min=eps).sqrt() # Compute cotangents of angles, of shape (sum(F_n), 3) A2, B2, C2 = A * A, B * B, C * C @@ -112,7 +112,7 @@ def cot_laplacian( cotb = (A2 + C2 - B2) / area cotc = (A2 + B2 - C2) / area cot = torch.stack([cota, cotb, cotc], dim=1) - cot /= 4.0 + cot = cot / 4.0 # Construct a sparse matrix by basically doing: # L[v1, v2] = cota @@ -127,7 +127,7 @@ def cot_laplacian( # L[v2, v1] = cota # L[v0, v2] = cotb # L[v1, v0] = cotc - L += L.t() + L = L + L.t() # For each vertex, compute the sum of areas for triangles containing it. idx = faces.view(-1) diff --git a/tests/test_laplacian_matrices.py b/tests/test_laplacian_matrices.py index f04c6dfe..ca52a5c8 100644 --- a/tests/test_laplacian_matrices.py +++ b/tests/test_laplacian_matrices.py @@ -118,3 +118,14 @@ class TestLaplacianMatrices(TestCaseMixin, unittest.TestCase): Lnaive[e1, e0] += w01 self.assertClose(L.to_dense(), Lnaive) + + def test_cot_laplacian_backward(self): + """Regression: in-place ops in _cot_laplacian_python break autograd.""" + verts = torch.tensor( + [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]], + requires_grad=True, + ) + faces = torch.tensor([[0, 1, 2], [1, 3, 2]]) + L, inv_areas = cot_laplacian(verts, faces) + (L.to_dense().sum() + inv_areas.sum()).backward() + assert verts.grad is not None