Skip to content

ENH: Make functions in dot family and related to support user-defined data types #30793

Open

Description

@SwayamInSync

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.
  • einsum has even more data-corruption issues as at
    if (sop == NULL) {
    PyErr_SetString(PyExc_TypeError,
    "invalid data type for einsum");
    }
    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.

Activity

  1. ngoldbaum commented on Feb 6, 2026

    @ngoldbaum
    Member

    Ping @mhvk @WarrenWeckesser - I suspect you both have opinions here.

  2. mhvk commented on Feb 7, 2026

    @mhvk
    Contributor

    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.

  3. SwayamInSync commented on Feb 7, 2026

    @SwayamInSync
    MemberAuthor

    @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.

  4. seberg commented on Feb 8, 2026

    @seberg
    Member

    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.)

  5. abslock128w commented on Feb 17, 2026

    @abslock128w
    Contributor

    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?

  6. seberg commented on Feb 17, 2026

    @seberg
    Member

    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)

  7. abslock128w commented on Feb 17, 2026

    @abslock128w
    Contributor

    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?

  8. abslock128w commented on Feb 18, 2026

    @abslock128w
    Contributor

    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

  9. added a commit that references this issue on Jun 6, 2026
    05dfda1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions