-
Notifications
You must be signed in to change notification settings - Fork 132
Add numpy-like vecdot
, vecmat
and matvec
helpers
#1250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
pytensor/tensor/math.py
Outdated
def vecdot( | ||
x1: "ArrayLike", | ||
x2: "ArrayLike", | ||
axis: int = -1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was surprised by this, didn't know vecdot had axis and in the numpy docstrings it's buried so it may be just something cropping up from the gufunc. Not sure we want to add it, would need to check numpy manually.
Also the original tests (didn't check yet in this PR) weren't comparing with vecdot which was suspicious ?
pytensor/tensor/math.py
Outdated
x2 = as_tensor_variable(x2) | ||
|
||
# Handle negative axis | ||
if axis < 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are numpy helpers we use to handle this
pytensor/tensor/math.py
Outdated
# Move the axes to the end for dot product calculation | ||
x1_perm = list(range(x1.type.ndim)) | ||
x1_perm.append(x1_perm.pop(x1_axis)) | ||
x1_transposed = x1.transpose(x1_perm) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was some discussion on numpy about this using the conjugate transpose, not sure if true or relevant
pytensor/tensor/math.py
Outdated
@@ -2841,6 +2841,138 @@ def matmul(x1: "ArrayLike", x2: "ArrayLike", dtype: Optional["DTypeLike"] = None | |||
return out | |||
|
|||
|
|||
def vecdot( | |||
x1: "ArrayLike", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should use TensorLike
pytensor/tensor/math.py
Outdated
x2: "ArrayLike", | ||
axis: int = -1, | ||
dtype: Optional["DTypeLike"] = None, | ||
): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing output type
pytensor/tensor/math.py
Outdated
|
||
Returns | ||
------- | ||
out : ndarray |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrong
pytensor/tensor/math.py
Outdated
----- | ||
This is similar to `dot` but with broadcasting. It computes the dot product | ||
along the specified axes, treating these as vectors, and broadcasts across | ||
the remaining axes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ask it to add a docstring example
tests/tensor/test_math.py
Outdated
|
||
x_val = random(2, 3, 4, rng=rng).astype(config.floatX) | ||
y_val = random(2, 3, 4, rng=rng).astype(config.floatX) | ||
np.testing.assert_allclose(f(x_val, y_val), np.sum(x_val * y_val, axis=2)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should compare against the numpy equivalent function
tests/tensor/test_math.py
Outdated
expected = np.array([np.dot(x_val[i], y_val[i]) for i in range(2)]) | ||
np.testing.assert_allclose(f(x_val, y_val), expected) | ||
|
||
def test_matmul(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already had matmul tests
tests/tensor/test_math.py
Outdated
y_val = random(2, 3, 4, rng=rng).astype(config.floatX) | ||
np.testing.assert_allclose(f(x_val, y_val), np.sum(x_val * y_val, axis=2)) | ||
|
||
def test_matvec(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can vecmat and matvec tests be combined with a parametrize? Also vecdot if we drop the axis thing
My comments apply in more places than where I mentioned it |
pytensor/tensor/math.py
Outdated
x2_axis = axis | ||
|
||
# Move the axes to the end for dot product calculation | ||
x1_perm = list(range(x1.type.ndim)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's moveaxis for this
Changed my mind, let's not bother with axis (or keepdims for that matter). This is a bigger thing that goes beyond these methods and which we should habdle systematically. |
Codecov ReportAttention: Patch coverage is
❌ Your patch status has failed because the patch coverage (73.33%) is below the target coverage (100.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #1250 +/- ##
==========================================
- Coverage 81.99% 81.99% -0.01%
==========================================
Files 188 188
Lines 48608 48623 +15
Branches 8688 8691 +3
==========================================
+ Hits 39857 39868 +11
- Misses 6586 6588 +2
- Partials 2165 2167 +2
🚀 New features to boost your workflow:
|
pytensor/tensor/math.py
Outdated
>>> x = pt.matrix("x") | ||
>>> y = pt.matrix("y") | ||
>>> z = pt.vecdot(x, y) | ||
>>> # Equivalent to np.sum(x * y, axis=-1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should say equivalent to np.vecdot
pytensor/tensor/math.py
Outdated
Examples | ||
-------- | ||
>>> import pytensor.tensor as pt | ||
>>> import numpy as np |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it importing numpy if not used
pytensor/tensor/math.py
Outdated
>>> import pytensor.tensor as pt | ||
>>> import numpy as np | ||
>>> # Matrix-vector product | ||
>>> A = pt.matrix("A") # shape (M, K) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pt.matrix
/pt.tensor3
accepts shapes. Use concrete shapes and then comment on the concrete shape the output would have. Same for other examples.
tests/tensor/test_math.py
Outdated
@@ -2076,6 +2079,86 @@ def is_super_shape(var1, var2): | |||
assert is_super_shape(y, g) | |||
|
|||
|
|||
class TestMatrixVectorOps: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests are still overly-complicated. Here is what I would do. Create one test for the 3 new functions, and just call it once with two matrices, that will always have at least one batch dimension.
Compare with the respective numpy function. Because we test on numpy<2.0 in some jobs of the CI add a pytest.skip
based on the numpy version.
Taking a step back. Is there a way that the loop can be facilitated other than me giving reviews and you forwarding them to whatever environment you're using? Like if you don't personally care about the changes, you are just in the middle @twiecki. Can I re-prompt it myself from Github? For the amount of work I had reviewing it, I could have done it myself. But obviously that's why it's marked as a beginner friendly issue. It's only worth it if someone learns by doing this simple PR and can then contribute in a harder PR. That will not be the case for Claude. Could it be the original issue needs to be more fleshed out. Could the LLMs help in that instead? I still think the place they can help the most right now is docs stuff, not code. But feel free to prove me wrong. |
@ricardoV94 Very good point. Mainly I wanted to test how good this is and what would happen. It seems like it's still not good enough to be used by someone semi-blindfolded (which is what I mostly did). I would imagine though that if used by yourself it could make you a lot more productive because you'd just directly be able to tell it and have a tighter feedback loop. In sum, I agree it's a bit sobering. It's like 90% there maybe but those final 10% still cause too much back-and-forth. Granted, this is a very complex code-base with it's own standards. I've had better experiences with simpler and smaller code-bases and new projects. Docs is a really good idea, any particular place/issue I should look at? |
Thinking how to make use of this. Perhaps it could be used just as a blueprint. Like the biggest hurdle for new users is probably imagining how a solution will look like and what files need to be changed, so maybe we narrow the goal of the bot to this, I don't know if as a draft PR or comments on the original issues. I would like the bot to have clear goals:
In terms of workflow. Can we have a bot that is integrated with GH? Like can I ask it to attempt a PR by commenting on the issue. Can it read the results of the CI and propose new changes / with explanations of what failed and how it refined the answer. Can it automatically interact with reviews? For doing reviews itself: It needs to be more strict/ adversial, while ignoring nitpicks altogether. I don't want to test the reviews with outsiders because it can be off-putting, but happy to let it try and review my PRs. |
For docs: can it solve this PR that got stuck? #830 |
@ricardoV94 I gave it another go with your latest feedback, seems like a waste not to get this functionality in. Looks like our CI is borked though. |
pytensor/tensor/math.py
Outdated
>>> x_batch = pt.matrix("x") # shape (3, 5) | ||
>>> y_batch = pt.matrix("y") # shape (3, 5) | ||
>>> z_batch = pt.vecdot(x_batch, y_batch) # shape (3,) | ||
>>> # Equivalent to numpy.sum(x_batch * y_batch, axis=-1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still saying equivaent to numpy.sum
or numpy.dot
instead of numpy.vecmat
. I guess it never saw those because they are too recent and doesn't believe in them?
pytensor/tensor/math.py
Outdated
x1 = as_tensor_variable(x1) | ||
x2 = as_tensor_variable(x2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed in any of the Ops
x1 = as_tensor_variable(x1) | |
x2 = as_tensor_variable(x2) |
pytensor/tensor/math.py
Outdated
|
||
def matvec( | ||
x1: "TensorLike", x2: "TensorLike", dtype: Optional["DTypeLike"] = None | ||
) -> "TensorVariable": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think TensorVariable
needs to be in string. Not sure about TensorLike
tests/tensor/test_math.py
Outdated
), | ||
], | ||
) | ||
def test_matrix_vector_ops(self, func, x_shape, y_shape, np_func, batch_axis): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
batch_axis not used
tests/tensor/test_math.py
Outdated
# Create PyTensor variables with appropriate dimensions | ||
if len(x_shape) == 1: | ||
x = vector() | ||
elif len(x_shape) == 2: | ||
x = matrix() | ||
else: | ||
x = tensor3() | ||
|
||
if len(y_shape) == 1: | ||
y = vector() | ||
elif len(y_shape) == 2: | ||
y = matrix() | ||
else: | ||
y = tensor3() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplify:
x = tensor(shape=x_shape)
y = tensor(shape=y_shape)
tests/tensor/test_math.py
Outdated
y = tensor3() | ||
|
||
# Test basic functionality | ||
z = func(x, y) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call this pt_func
to distinguish from np_func
tests/tensor/test_math.py
Outdated
"func,x_shape,y_shape,np_func,batch_axis", | ||
[ | ||
# vecdot | ||
(vecdot, (5,), (5,), lambda x, y: np.dot(x, y), None), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
np_func
should just be numpy equivalents (np.vecmat, np.matvec...). Skip if numpy < 2.0 because they didn't exist. No need for these lambda x, y, which may be incorrect anyway
tests/tensor/test_math.py
Outdated
z = func(x, y) | ||
f = function([x, y], z) | ||
|
||
x_val = random(*x_shape, rng=rng).astype(config.floatX) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not using the rng
key it created on top
tests/tensor/test_math.py
Outdated
|
||
# Test with dtype parameter (to improve code coverage) | ||
# Use float64 to ensure we can detect the difference | ||
z_dtype = func(x, y, dtype="float64") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An integer dtype would be a much stronger test
pytensor/tensor/math.py
Outdated
-------- | ||
>>> import pytensor.tensor as pt | ||
>>> # Vector-matrix product | ||
>>> v = pt.vector("v") # shape (3,) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be my suggestion. For inputs specify explicitly, for intermediate mention in comments
>>> v = pt.vector("v") # shape (3,) | |
>>> v = pt.vector("v", shape=(3,)) |
The jax failure is already fixed upstream |
Add three new functions that expose the underlying Blockwise operations: - vecdot: Computes dot products between vectors with broadcasting - matvec: Computes matrix-vector products with broadcasting - vecmat: Computes vector-matrix products with broadcasting These match the NumPy API for similar operations and complement the existing matmul function. Each comes with appropriate error handling, parameter validation, and comprehensive test coverage. Fixes #1237
- Remove redundant dimension checks that Blockwise already handles - Streamline test cases while keeping essential coverage - Based on PR feedback from Ricardo 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
- Remove axis parameter from vecdot (no longer needed) - Update type annotations to use TensorLike - Add proper return type annotations - Improve docstrings with examples - Simplify test implementation and use pytest.parametrize - Use simpler implementation for batched operations 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
- The `matmul` function was already well-tested elsewhere - Focus our tests specifically on the three new helper functions 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
- Improve docstrings with concrete shape examples - Explicitly state equivalence to NumPy functions - Simplify tests into a single parametrized test - Add dtype parameter test to ensure full coverage - Keep implementation minimal by relying on Blockwise checks 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
- Update type annotations to remove unnecessary quotes - Improve docstrings with concrete shape examples - Use NumPy equivalents (vecdot, matvec, vecmat) in docstrings - Simplify function implementations by removing redundant checks - Substantially simplify tests to use a single test with proper dimensions - Use proper 'int32' dtype test for better coverage - Update test to handle both NumPy<2.0 and NumPy>=2.0 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
d3018d2
to
35e7be1
Compare
- Remove as_tensor_variable calls as operations already handle conversion - Blockwise constructors handle tensor conversion internally 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
- Convert test class to standalone function - Remove unnecessary class-based structure for single test - Keep the same test functionality - Address PR feedback 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
- Use config.floatX for test tensor dtypes - Explicitly specify tensor dtype to match test values - Fix CI build errors related to dtype mismatches - Create test values before tensor variables 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
-------- | ||
>>> import pytensor.tensor as pt | ||
>>> # Vector dot product with shape (5,) inputs | ||
>>> x = pt.vector("x", shape=(5,)) # shape (5,) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It really has no sense of brevity, the comment is completely superfluous. Not blocking on this, but I doubt any human would be this "meh"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, claude 3.7 is known for being verbose and over-eager.
vecdot
, vecmat
and matvec
helpers
Summary
Test plan
Fixes #1237
🤖 Generated with Claude Code
📚 Documentation preview 📚: https://pytensor--1248.org.readthedocs.build/en/1248/
Summary
Test plan
Fixes #1237 (continuation of #1248)
🤖 Generated with Claude Code
📚 Documentation preview 📚: https://pytensor--1250.org.readthedocs.build/en/1250/