You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ENH: Make functions in dot family and related to support user-defined data types #30793
Currently some functions from dot family are not supported to work with user-defined data types. Following table summarizes the available support
Function
Current Implementation
Support
matmul
gufunc (n?,k),(k,m?)->(n?,m?)
✅
vecdot
gufunc (n),(n)->()
✅
matvec
gufunc (m,n),(n)->(m)
✅
vecmat
gufunc (n),(n,m)->(m)
✅
dot
C API + PyArray_ObjectType
❌
inner
C API + PyArray_ObjectType
❌
vdot
C API + PyArray_ObjectType
❌
correlate/correlate2
C API + PyArray_ObjectType
❌
np.einsum (2-op, optimized)
Python bmm_einsum → matmul
✅
np.einsum (c_einsum reductions)
PyArray_EinsteinSum()
❌ (segfault / silent wrong results)
Most importantly , tensordot, multi_dot and inner all delegate to dot
There are multiple layers in different function that cause the issues
Functions relying on PyArray_ObjectType call, which further calls NPY_DT_is_legacy() and raises TypeError for user-defined dtypes.
np.einsum specifically c_einsum uses get_sum_of_products_function which explicitly rejects non-legacy types: if (type_num >= NPY_NTYPES_LEGACY) return NULL, which can segfault.
it sets the error but falls through finish instead of goto fail returning the zero initialized output.
We cannot delegate dot function to matmul as for the input case of N-D x M-D it does an outer product over batch dims, while matmul broadcasts them. However it is possible to represent all the different cases of np.dot via the combination of other ufunc calls but I think a straightforward approach might be to let the developer implement and register with some hook.
Possible working direction
Introduce dot as a proper gufunc with signature (n?,k),(k,m?)->(n?,m?) same signature as matmul. Dtype authors register a single loop for dot via PyUFunc_AddLoopFromSpec.
That's a slightly frightening list... When we introduced matmul, part of the point was to (eventually) get rid of dot, since the way it deals with dimensions is so different from standard broadcasting. Given that, my sense is that we should start (very softly) deprecating dot -- perhaps have the paths where its behaviour is consistent with broadcasting call matmul or equivalent (and let that handle the dtypes too), and deprecate other paths (in which one would just error on user types). A simpler start might be to special-case the error message for user types with a suggestion to use matmul, vecmat, etc., instead, as appropriate.
To me, einsum is a different case: we should make an effort to fully support user types. Possibly this can be done internally by calling matmul, etc., implementations for the individual steps?
Similarly, correlate should just be fixed. Is convolve also bad? (it calls multiarray.correlate2 instead of multiarray.correlate, but I haven't checked the differences...)
EDIT: tensordot is part of the Array API, so important to support that properly: easiest might be for it to start using matmul internally.
@mhvk I get your point and maybe deprecation can be a long shot as there might be a lot of code using dot out there.
So proposing one more idea: Every dot input case can be delegate to some ufunc
Case
Dispatch to
either is 0-D
multiply (already done in current C)
1-D × 1-D
vecdot
≥2-D × 1-D
matvec
1-D × ≥2-D
vecmat
2-D × 2-D
matmul
N-D × M-D
reshape to 2D → matmul → reshape back (the tensordot trick)
For performance, this should be equal or better than today. The current N-D × M-D C path runs a nested iterator loop calling a 1D dotfunc per output element, but tensordot already does this better, reshapes both inputs to 2D, calls dot, and reshapes back. We'd just close the loop by having dot call matmul instead, so the chain becomes tensordot -> dot -> matmul with everything going through ufunc dispatch.
This alone would fix dot, inner (transposes then calls dot), tensordot (reshapes then calls dot), and multi_dot (calls dot) -- all without any user-visible behavior change. vdot needs a small separate fix (conjugate + flatten + vecdot).
The root cause of all the user-dtype breakage is PyArray_ObjectType, which rejects non-legacy dtypes. By dispatching to ufuncs we sidestep this entirely.
I also think this could just live at the Python level (similar to how tensordot is already implemented in Python), keeping the C path as a fast path for legacy dtypes if we want to be conservative about performance changes.
We cannot delegate dot function to matmul as for the input case of N-D x M-D it does an outer product over batch dims, while matmul broadcasts them.
I think even this we can map to matmul by reshaping the inputs to make it an outer product. In theory np.matmul.outer() could even do that, just in practice doing it generally for a gufuncs requires munching the gufunc axes (which likely makes that too tedious to be worthwhile).
A simpler start might be to special-case the error message for user types with a suggestion to use matmul, vecmat, etc., instead, as appropriate.
This seems rather reasonable to me. .dot() is almost always superseded by either matmul or @ after all.
But yeah, quite a bit of internal use may have to shift to use the ufuncs (and at the same time, we may have to make sure e.g. ml_dtypes actually implement those, but that will be easy enough).
That said, I wouldn't be surprised if it isn't easier to make dot silently fall back to matmul if it cannot find a dot arrfunc. Just because that makes tensordot and correlate work without any other subtleties to worry about.
To me, einsum is a different case: we should make an effort to fully support user types. Possibly this can be done internally by calling matmul, etc., implementations for the individual steps?
Einsum would be good indeed. Mapping to existing ufuncs loops would be nicest and I guess the most flexible.
In principle, I guess we could also have ArrayMethods representing other loops if need be or just a dedicated ArrayMethod like thing...
(Einsum has multiply N inputs, which doesn't map, but at the same time is maybe simpler, because I think it always assumes a single result_type is OK. I am sure we can register such strided loops/a get_loop in some way, it might just not be quite an ArrayMethod, unfortunately.)
hey! so i went through the actual code and i think these are worth splitting into two separate tracks since the fixes are quite different.
for einsum the fall-through bug is a bit more subtle than it looks. in PyArray_EinsteinSum, when sop == NULL the error gets set but execution falls through to finish: instead of fail:, which means it still does Py_INCREF(ret) and returns the zero-initialized output array. so the caller gets a silent wrong result instead of an exception. would fixing that fall-through (goto fail instead of continuing) be a reasonable first patch? it's isolated and doesn't touch any of the bigger ufunc-routing design.
for the dot family i can see in multiarraymodule.c that PyArray_InnerProduct, PyArray_MatrixProduct2, and both PyArray_Correlate/PyArray_Correlate2 all go through PyArray_ObjectType as their first step, so they all hit the same wall. the ufunc dispatch table approach would fix all of them in one go. would it make sense to start with just adding a better error message for user dtypes as @seberg suggested, while the dispatch design gets figured out?
I suspect the missing goto is wrong but actually caught later via a kinda unnecessary double-check. (That just means it's tedious to add an actual test, but it would be good to still to makek sure we have a test that hits the path if we don't have that, e.g. with the rational dtype).
Well, I think we can't be 100% but since Marten and I agree that an error is a decent first step, sure. It would have to be clear that the user can try using @, matmul, etc.
(there is no guarantee it will be defined after all)
makes sense
1.ill cover dot, inner, vdot and correlate/correlate2
2.ill also go ahead and add a test for the einsum path using rational dtype and other's? since u mentioned it would be good to have coverage there...
3.shud i add all this in one PR or shud it be separated one for the error msg and the other for the test?
4.so for the einsum test should i just check that it raises a proper error for user-defined dtypes? and would rational dtype be the right way to trigger that path or is there something better to use?
I suspect the missing goto is wrong but actually caught later via a kinda unnecessary double-check. (That just means it's tedious to add an actual test, but it would be good to still to makek sure we have a test that hits the path if we don't have that, e.g. with the rational dtype).
Well, I think we can't be 100% but since Marten and I agree that an error is a decent first step, sure. It would have to be clear that the user can try using @, matmul, etc. (there is no guarantee it will be defined after all)
Hey there,
I have created a PR for the error msg as u mentioned... do check it out and lmk if it seems plausible
Activity
ngoldbaum commentedon Feb 6, 2026
Ping @mhvk @WarrenWeckesser - I suspect you both have opinions here.
mhvk commentedon Feb 7, 2026
That's a slightly frightening list... When we introduced
matmul, part of the point was to (eventually) get rid ofdot, since the way it deals with dimensions is so different from standard broadcasting. Given that, my sense is that we should start (very softly) deprecatingdot-- perhaps have the paths where its behaviour is consistent with broadcasting callmatmulor equivalent (and let that handle the dtypes too), and deprecate other paths (in which one would just error on user types). A simpler start might be to special-case the error message for user types with a suggestion to usematmul,vecmat, etc., instead, as appropriate.To me,
einsumis a different case: we should make an effort to fully support user types. Possibly this can be done internally by callingmatmul, etc., implementations for the individual steps?Similarly,
correlateshould just be fixed. Isconvolvealso bad? (it callsmultiarray.correlate2instead ofmultiarray.correlate, but I haven't checked the differences...)EDIT:
tensordotis part of the Array API, so important to support that properly: easiest might be for it to start usingmatmulinternally.SwayamInSync commentedon Feb 7, 2026
@mhvk I get your point and maybe deprecation can be a long shot as there might be a lot of code using
dotout there.So proposing one more idea: Every
dotinput case can be delegate to some ufuncmultiply(already done in current C)vecdotmatvecvecmatmatmulmatmul→ reshape back (the tensordot trick)For performance, this should be equal or better than today. The current
N-D × M-DC path runs a nested iterator loop calling a 1Ddotfuncper output element, buttensordotalready does this better, reshapes both inputs to 2D, callsdot, and reshapes back. We'd just close the loop by havingdotcallmatmulinstead, so the chain becomestensordot -> dot -> matmulwith everything going through ufunc dispatch.This alone would fix
dot,inner(transposes then callsdot),tensordot(reshapes then callsdot), andmulti_dot(callsdot) -- all without any user-visible behavior change.vdotneeds a small separate fix (conjugate + flatten +vecdot).The root cause of all the user-dtype breakage is
PyArray_ObjectType, which rejects non-legacy dtypes. By dispatching to ufuncs we sidestep this entirely.I also think this could just live at the Python level (similar to how
tensordotis already implemented in Python), keeping the C path as a fast path for legacy dtypes if we want to be conservative about performance changes.seberg commentedon Feb 8, 2026
I think even this we can map to
matmulby reshaping the inputs to make it an outer product. In theorynp.matmul.outer()could even do that, just in practice doing it generally for a gufuncs requires munching the gufunc axes (which likely makes that too tedious to be worthwhile).This seems rather reasonable to me.
.dot()is almost always superseded by eithermatmulor@after all.But yeah, quite a bit of internal use may have to shift to use the ufuncs (and at the same time, we may have to make sure e.g.
ml_dtypesactually implement those, but that will be easy enough).That said, I wouldn't be surprised if it isn't easier to make
dotsilently fall back tomatmulif it cannot find adotarrfunc. Just because that makestensordotandcorrelatework without any other subtleties to worry about.Einsum would be good indeed. Mapping to existing ufuncs loops would be nicest and I guess the most flexible.
In principle, I guess we could also have
ArrayMethods representing other loops if need be or just a dedicatedArrayMethodlike thing...(Einsum has multiply N inputs, which doesn't map, but at the same time is maybe simpler, because I think it always assumes a single
result_typeis OK. I am sure we can register such strided loops/aget_loopin some way, it might just not be quite anArrayMethod, unfortunately.)abslock128w commentedon Feb 17, 2026
hey! so i went through the actual code and i think these are worth splitting into two separate tracks since the fixes are quite different.
for
einsumthe fall-through bug is a bit more subtle than it looks. inPyArray_EinsteinSum, whensop == NULLthe error gets set but execution falls through tofinish:instead offail:, which means it still doesPy_INCREF(ret)and returns the zero-initialized output array. so the caller gets a silent wrong result instead of an exception. would fixing that fall-through (goto failinstead of continuing) be a reasonable first patch? it's isolated and doesn't touch any of the bigger ufunc-routing design.for the dot family i can see in
multiarraymodule.cthatPyArray_InnerProduct,PyArray_MatrixProduct2, and bothPyArray_Correlate/PyArray_Correlate2all go throughPyArray_ObjectTypeas their first step, so they all hit the same wall. the ufunc dispatch table approach would fix all of them in one go. would it make sense to start with just adding a better error message for user dtypes as @seberg suggested, while the dispatch design gets figured out?seberg commentedon Feb 17, 2026
I suspect the missing goto is wrong but actually caught later via a kinda unnecessary double-check. (That just means it's tedious to add an actual test, but it would be good to still to makek sure we have a test that hits the path if we don't have that, e.g. with the
rationaldtype).Well, I think we can't be 100% but since Marten and I agree that an error is a decent first step, sure. It would have to be clear that the user can try using
@,matmul, etc.(there is no guarantee it will be defined after all)
abslock128w commentedon Feb 17, 2026
makes sense
1.ill cover dot, inner, vdot and correlate/correlate2
2.ill also go ahead and add a test for the einsum path using rational dtype and other's? since u mentioned it would be good to have coverage there...
3.shud i add all this in one PR or shud it be separated one for the error msg and the other for the test?
4.so for the einsum test should i just check that it raises a proper error for user-defined dtypes? and would rational dtype be the right way to trigger that path or is there something better to use?
abslock128w commentedon Feb 18, 2026
Hey there,
I have created a PR for the error msg as u mentioned... do check it out and lmk if it seems plausible
ENH: support the dot family for new-style user DTypes