Repository navigation
perf(operator): 1.5x faster fast_grad for small LabelTensor inputs - #855
Merged
GiovanniCanali merged 1 commit intoNov 7, 2026
Merged
Conversation
fast_grad called LabelTensor.extract for each output component and ran every torch call through LabelTensor.__torch_function__. _scalar_grad also looked up the input label indices once per component and copied the gradient with an index even when d asked for all inputs in order. Slice the plain output tensor by column index, compute the input label indices once, skip the index when d equals the input labels, skip torch.cat for one component, and call torch.autograd.grad with the LabelTensor dispatch turned off. Values, labels and higher-order gradients are unchanged. test_gradient-like workload: 147.9 ms -> 98.9 ms (33% less time). Co-Authored-By: Aiden <aiden@weco.ai>
GiovanniCanali
approved these changes
Nov 7, 2026
GiovanniCanali
left a comment
Collaborator
There was a problem hiding this comment.
Thank you, @dexhunter! This looks great. Excellent catch identifying the performance bottleneck caused by the unnecessary use of LabelTensor in the _scalar_grad utility method.
I believe this improvement will have a significant impact, particularly on physics-informed models, where gradients are computed extensively during each training epoch.
Collaborator
|
Just a quick note, @dexhunter: I've changed the target branch for this PR from For future development and contributions to the package, we recommend using the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
fast_gradinpina/_src/core/operator.py, whichpina.operator.gradcalls for every gradient.LabelTensordispatch aroundautograd.grad, and skips unneeded index andcatcopies.tests/test_operator.py::test_gradient, median of 3 timed runs on an isolated core): 147.9 ms -> 98.9 ms, 33% less time.Refs #854
Description
This PR fixes #854. It changes only
fast_gradand its helper_scalar_grad.What & Why
Before,
fast_gradcalledLabelTensor.extractfor each output component. Every torch call on aLabelTensoralso went through__torch_function__._scalar_gradlooked up the input label indices again for each component. It also copied the gradient with an index, even whendasked for all inputs in order.Now
fast_gradtakes the plain tensor once and slices each component by its column index. It computes the input label indices once and passes them to_scalar_grad. It skips the index whendequals the input labels, and skipstorch.catfor one component._scalar_gradrunstorch.autograd.gradwith the dispatch turned off, so the gradient comes back as a plain tensor. The math and the input object stay the same, so values, labels and higher-order gradients do not change.Changes
fast_grad: use one shared path for scalar and vector outputs, with column slices and precomputed label indices._scalar_grad: take column indices (orNonefor all inputs) and return a plain tensor.Results
Python 3.12.13, torch 2.15.0+cpu, Linux x86_64, one thread, isolated core, best of 7 runs, median of 3 rounds:
grad, 1 input, 2 outputs, 20 rowsgrad, 1 input, 1 output, 20 rowsgrad, 2 inputs, 2 outputs, 20 rowsgrad, 3 inputs, 3 outputs,d=["x", "z"], 1000 rowsgrad, 1 input, 2 outputs, 1000000 rows (math dominates, small gain)test_gradient(mostly 20-row, 2-output cases)Why this is safe and easy to maintain
The diff is small and replaces the two scalar/vector branches with one shorter path. Gradient values, dtypes, shapes, labels and
create_graph=Truebehaviour stay the same.tests/test_operator.pychecks grad, div, laplacian and advection, also for higher-order derivatives, and all tests pass.Validation
pytest tests/test_label_tensor/test_label_tensor.py tests/test_condition/test_input_target_condition.py tests/test_operator.py tests/test_condition/test_data_condition.py-> 103 passed.pytest tests/test_operator.py tests/test_equation tests/test_equation_zoo tests/test_solver tests/test_problem_zoo tests/test_label_tensor-> 664 passed.black --line-length 80leaves the file unchanged.python -m timeit -s "import torch; from pina import LabelTensor; from pina.operator import grad; x = LabelTensor(torch.rand(20, 1), ['x']).requires_grad_(); u = LabelTensor(torch.cat([x**2, x**3], -1), ['u', 'v'])" "grad(u, x)"Checklist
AI assistance
OpenAI Codex (gpt-6.1-sol) wrote the candidate versions of these two functions. Weco Observe tracked and scored them. A Claude Code agent picked a short, readable version that was clearly faster, and ran the tests and benchmarks above.
Weco share link: https://dashboard.weco.ai/share/5TM-D6CrkwpPBZFoFVh6IvdS7hwY-6M6