Repository navigation
ENH: make matmul into a ufunc - #12219
Conversation
df5e819 to
3703c09
Compare
|
It would be good to be able to deal with stacked vectors as well as stacked matrices. I started with four functions: |
|
If I recall correctly, the agreement on the mailing list was to make We should think about how single gufunc interacts with For the sake of clarity it still might be better to have alternative gufuncs like |
|
Here is the asv benchmark comparison run from commit 3703c095 to 97df928 (master just before this commit) Note that one of the benchmarks does not run. This is due to the following code segfaulting. Now debugging this (only fails for |
|
@shoyer - that is an intriguing suggestion, to use Do agree that having |
|
valgrind says it is |
mhvk
left a comment
There was a problem hiding this comment.
Tried finding your bug, and found one mistake, but I think it is unrelated. Could it be that you should use is2_n? (see in-line comment) Or is perhaps the assumption of contiguous data broken?
|
I found the segfault, but in doing so discovered that my cblas routines do not work with non-contiguous data. |
|
All the benchmarks run to completion, here are the latest results. There are a few regressions, and a few slight improvements |
|
Only the first seems worth checking in a bit more detail. That's a large regression for a fairly common case (A * AT) |
|
Commit cafcfa4dc improved the Edit: misinterpreted the results |
One of the problems is that there are a number of optimizations that can be made that do not fit nicely into the ufunc scheme, ufuncs are too low level. For instance, a stack_of_matrices X single_matrix can be optimized if the stacked matrices are reshaped so that it is simply matrix X matrix, same with stack of vectors. My own preference would be to implement a single matmat ufunc and implement |
Isn't that essentially the difference between |
|
@mattip I still need to make a careful review, before writing out my thoughts. |
b14424e to
7c62240
Compare
|
I think matmul as a ufunc is ready for review. The loops cast M, N, and P from npy_intp (8 bytes) to int (4 bytes) in the cblas matrixmatrix routines, but did so in the past as well, and dot also suffers from this casting, so I am leaving that for a future issue/PR. Speed improvements could be made, but the code seems to be as fast as the versions on master, so this too should be dealt with in subsequent work. |
There was a problem hiding this comment.
Should there be some sort of error handling here or should these be dropped? Just asking as the comment says this shouldn't be run yet the branch is here.
Also same question for the branch below.
There was a problem hiding this comment.
I'd use an assert here too, and also move the default definitions of trans1 and lda here (from lines 111 and 118).
There was a problem hiding this comment.
assert in C will abort in debug and be ignored in release builds. There is no error handling in inner loops. I added a check before this call to ensure the input is contiguous.
Also moved the definitions.
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Is this worth it vs just letting python emit the error?
There was a problem hiding this comment.
I tend to agree to just let python error.
There was a problem hiding this comment.
The error for np.arange(10) @ 3 then becomes "ValueError: matmul: Input operand 0 does not have enough dimensions (has 0, gufunc core with signature (n?,k),(k,m?)->(n?,m?) requires 1)"
Will the typical user understand what went wrong?
There was a problem hiding this comment.
3 @ 3 errors with "TypeError: unsupported operand type(s) for @: 'int' and 'int'"
There was a problem hiding this comment.
The TypeError would definitely be fine. For the other, it does support keeping the error; on the other hand, really, when something goes wrong, how hard is it to do np.matmul?...
There was a problem hiding this comment.
np.matmul(np.arange(10), 3) will also reach this call, and will error.
In commit 98c8ac4cd221 I removed the call to PyErr_SetString, we can revert it if desired.
mhvk
left a comment
There was a problem hiding this comment.
@mattip - I went through with a reasonably fine-toothed comb, and found some little bits, but nothing too major. The main question I have is whether if cblas is used, it is reasonable to copy the whole input array (a possibly very large stack of matrices) rather than copy matrix by matrix. I must say my tendency would be to just not use cblas for non-contiguous data. Anyway, that perhaps can be decided after - since I think you currently just reproduce what was done before.
There was a problem hiding this comment.
dot(a,b) -> matmul(x1, x2)? Though I think it is also fine to rewrite the sentence to
it must be C-contiguous and have both the right type and the right dtype```
There was a problem hiding this comment.
maybe "but a subsequent call to matmul will..." (I'd parenthesize that, since it is somewhat of an aside).
|
@mhvk I would like to rebase and squash this since it has conflicts. Since that will make review more difficult, should I wait for more comments? |
|
Consider doing a merge instead of a rebase -- github preserves history for
reviewing better that way.
…On Tue, Nov 13, 2018 at 3:26 PM Matti Picus ***@***.***> wrote:
@mhvk <https://github.com/mhvk> I would like to rebase and squash this
since it has conflicts. Since that will make review more difficult, should
I wait for more comments?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#12219 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ABKS1rpt6_N9b2lcbqYfQBs1K9tsUMAaks5uu1U_gaJpZM4XwSxP>
.
|
There was a problem hiding this comment.
Are you sure these ns and ms aren't swapped?
There was a problem hiding this comment.
the assert was failing in the debug CI test, fixing.
There was a problem hiding this comment.
I'm wrong, these ms and ns are correct.
What's wrong is that the asserts on i1 should be within the if or if-else
There was a problem hiding this comment.
I rewrote the macro instead
There was a problem hiding this comment.
Perhaps rename to blasable_matrix_vector, and vector_blasable_matrix above?
There was a problem hiding this comment.
will do if there are more fixes
There was a problem hiding this comment.
Not strictly true, when you consider the 1d overloads. Maybe fine as is anyway.
There was a problem hiding this comment.
I think you want double backticks here, since single backticks tries to make a reference.
There was a problem hiding this comment.
For a separate doc cleanup PR: This would be a lot clearer if the dimensions were all different. (2, 3, 4) and (2, 4, 5) would show the behavior nicely.
There was a problem hiding this comment.
For a separate doc cleanup PR: spaces around commas for PEP8 - let's not train new users into going against PEP8 if we can avoid it.
There was a problem hiding this comment.
easy enough to clean up now
There was a problem hiding this comment.
Not a fan of this macro. I think I'd prefer a form that takes all the parameters, with a little more explanation. Also, may as well just use an inline function.
Perhaps something like:
/* -1 to be conservative, in case blas internally uses a for loop with an inclusive upper bound */
#define BLAS_MAXSIZE (NPY_MAX_INT - 1)
/**
* Return true if matrix with strides (s1, s2) and dimensions (n1, n2) can be used with blas
*/
static inline npy_bool
is_corder_blasable_2d(npy_intp s1, npy_intp s2, npy_intp n1, npy_intp n2, npy_intp itemsize) {
npy_intp step1;
if (!(s2 == itemsize)) {
/* second axis must be contiguous */
return NPY_FALSE;
}
if (!(s1 % itemsize == 0)) {
/* strides in the first axis must count whole elements */
return NPY_FALSE;
}
step1 = s1 / itemsize;
if (!(n1 <= step1) {
/* number of elements must not cause slices of the last axis to overlap */
return NPY_FALSE;
}
/* todo: can n1 or n2 be 0? */
if (!(n1 <= BLAS_MAXSIZE && n2 <= BLAS_MAXSIZE && step1 <= BLAS_MAXSIZE) {
/* dimensions must be representable in BLAS */
return NPY_FALSE;
}
return NPY_TRUE;
}
static inline npy_bool
is_forder_blasable_2d(npy_intp s1, npy_intp s2, npy_intp n1, npy_intp n2, npy_intp itemsize) {
/* just transpose the array */
return is_corder_blasable_2d(s2, s1, n2, n1, itemsize);
}
static inline npy_bool
is_blasable_1d(npy_intp s, npy_intp n, npy_intp itemsize) {
npy_intp step;
if (!(s1 % itemsize == 0)) {
/* strides must count whole elements */
return NPY_FALSE;
}
/* todo: can INCX be 0 or negative? */
/* todo: can N be 0? */
if (!(n <= BLAS_MAXSIZE && step <= BLAS_MAXSIZE) {
/* dimensions must fit in an int */
return NPY_FALSE;
}
return NPY_TRUE;
}
Yes, this is far more verbose, but I'd hope they'd be optimized to the same code, and this gives far more places for comments.
It's not clear to me whether c / f order is the right way to name these function, or whether is_forder_blasable is more useful than explicitly calling is_corder_blasable with the arguments in the other order.
There was a problem hiding this comment.
adopted a modified version of this
There was a problem hiding this comment.
could omit the == 1 after you remove the 2 above
There was a problem hiding this comment.
I don't think these three lines are correct, which is why I was pushing for a distinction between "is supported by blas" and "is supported by blas if I transpose it".
There was a problem hiding this comment.
ok, will extend the function
There was a problem hiding this comment.
This is not true - it needn't be a multiple, it just needs to exceed the length of the fast axis
There was a problem hiding this comment.
This should move into the if above.
962342f to
ae9bb7b
Compare
There was a problem hiding this comment.
Probably safer to use BLAS_MAX_SIZE here
ae9bb7b to
2bcc6e3
Compare
2bcc6e3 to
72f2abf
Compare
| if (op[i] == NULL) { | ||
| op[i] = operands[i]; | ||
| Py_INCREF(op[i]); | ||
| } |
There was a problem hiding this comment.
These lines are unrelated cleanup, right?
eric-wieser
left a comment
There was a problem hiding this comment.
Looks ready now - just one comment left about documenting the need for the non-default ufunc flags, which you explained well in person.
|
Fixed comment on non-default flags, ready for merge |
|
Thanks for the persistence, @mattip! |
|
Woohoo, thank you @mattip! |
|
Yay!! Thanks, @mattip! |
|
Thanks so much for working on this, @mattip! 😄 🎉 |
Replaces #11133.
Uses the extended generalized ufunc signature
(n?,k),(k,m?)->(n?,m?), a customPyUFunc_MatmulTypeResolverfunction, and new inner loop functions to implementmatmulas a true ufunc.Still known todo:
outkwarg), theufunc->op_flagwill be set to use writeback semantics. On master, this currently does not work since all the flags are overridden.core/_add_newdocs.pytoufunc_docstringsTheLeave this for a future PRTypeResolvershould already choose the proper vector-vector, vector-matrix, or matrix-matrix inner loop based on shape (maybe, needs benchmarking to see which is faster)npy_intpto blasintdo not overfloweinsum.Edit: update after commit 207fd8b3