Skip to content

WIP: ENH: Matmul as ufunc - #11133

Closed
mattip wants to merge 13 commits into
numpy:masterfrom
mattip:matmul-as-ufunc
Closed

mattip wants to merge 13 commits into
numpy:masterfrom
mattip:matmul-as-ufunc

Conversation

@mattip

@mattip mattip commented May 22, 2018 •

Copy link
Copy Markdown
Member

The discussion of issue #9028 yielded two different possible directions for allowing ndarray subclasses to override __matmul__ via __array_ufunc__. This is option 1 - allow a single ufunc matmul which will implement vector-vector, vector-matrix, matrix-vector, and matrix-matrix multiplication. In this PR, which builds on PR #11132 PR #11175, the implementation of matmul is converted to a ufunc.

Note the pull request incorporates the changes from pr #11132 so the changesets are repeated

What changed?

  • matmul is now implemented as a true ufunc, it appears in the umath's n_ops, can be set and gotten using PyArray_{G,S}etNumericOps, and its loops are defined in loops.c.src
  • tests were expanded to reflect this change

What is not finished?

What is next?

Edit: update status

@mattip

mattip commented Jun 5, 2018

Copy link
Copy Markdown
Member Author

Rebased off PR #11175. We have loops in cblas_matrixproduct, and in vdot.c, there must be a way to reuse them nicely without having to rewrite all this

@mhvk

mhvk commented Jun 6, 2018

Copy link
Copy Markdown
Contributor

Setup looks great; the trick will indeed be to have a nice way to choose BLAS etc for the inner loop.

@mattip

mattip commented Jun 6, 2018

Copy link
Copy Markdown
Member Author

Since matmul is such a core function, I ran a little comparision multiplying two 3x3 matrices via dot, einsum and matmul-as-a-ufunc. It turns out (no suprise) there is a cost to ufuncs. For this simple case, and without diving too deeply into cblas's gemm vs. a naive looping implementation, the ufunc case is roughly 2 times slower. Of course we are talking about very small numbers, around 1.2 microseconds vs. half of that, so I am going to push forward with making large arrays faster by reusing the dot loops here.

@mhvk

mhvk commented Jun 6, 2018

Copy link
Copy Markdown
Contributor

@mattip - I think the gufunc loops need some special-case speed-ups that the ufuncs already have, in particular for the case that there is no outer looping to be done, in which case the elementary function should just be called directly. But that is independent of matmul...

One thing to possibly consider for matmul - the loop selection could include whether or not to use the BLAS routines, or that choice could be made inside the loop function. The latter may be easier to programme, but the former might be better.

@mattip

mattip commented Jun 7, 2018

Copy link
Copy Markdown
Member Author

One of the trickier parts of this is what to do for non-contiguous memory. Just changing the benchmark code from func(a, a) to func(b[::2], [b::2]) where a is 3x3 and b is 6x6 the times for dot and einsum doubled but the time for "dumb" matmul did not change. Increasing the size to 50x50, still with the [::2] indexing, now dot is 3 times faster than matmul, and dot is over 4 times faster than einsum

@mhvk

mhvk commented Jun 7, 2018

Copy link
Copy Markdown
Contributor

Indeed, I think that when the strides are not contiguous, one probably should copy data over, but there is a question for what number of points this becomes useful. My guess would be that for small arrays the copy is so fast that it might not matter.

Aside: I've been implementing this in astropy - this includes a very basic 3x3 matrix multiplication routine, and in my implementation I check in the inner loop whether copies are necessary [1]. As I wrote above, I think it would be more elegant to do this outside, and select a copying or non-copying loop -- and pass on some temporary storage to the inner loop if needed -- though it may not matter that much speed-wise. Also, I think almost anything will be faster than the current call to einsum...

[1] https://gist.github.com/mhvk/a3c17f0db77b91151d50a0d278621d97#file-ufunc-c-L363

@mhvk

mhvk commented Jun 8, 2018

Copy link
Copy Markdown
Contributor

I like the last commit - but isn't that independent of matmul? I.e., should it be build on top of #11175? It would go with the version change...

@mattip
mattip force-pushed the matmul-as-ufunc branch 2 times, most recently from 7700bf9 to 7efa656 Compare June 28, 2018 19:13
@mattip

mattip commented Jul 3, 2018

Copy link
Copy Markdown
Member Author

it seems the matmul vector-vector case for column-row -> 2d array was not tested. I put a marker in each case of all the matmul loops, and that one came up missing.

@mattip

mattip commented Jul 3, 2018

Copy link
Copy Markdown
Member Author

note this is a wip, builds on top of master and pr #11175. Once that is merged I will clean up this branch

@mattip
mattip force-pushed the matmul-as-ufunc branch 3 times, most recently from 5cf6b60 to 2bcf710 Compare July 4, 2018 17:54
@mattip

mattip commented Jul 5, 2018

Copy link
Copy Markdown
Member Author

level 1 blas_{s,d,c,z}axy can work with non-contiguous data but require zeroing out y before the call, does it give a speedup vs. writing the operation y = a * x in naive C? I chose to go with the C loop, benchmarking needed.

@mattip

mattip commented Jul 5, 2018

Copy link
Copy Markdown
Member Author

Just noticed there is an optimization for a @ a.T in #6932 and a request for a@ a.H in #6951. Make sure these are not ruined by this refactoring

@mattip
mattip force-pushed the matmul-as-ufunc branch from c80c88b to 089b1a2 Compare July 23, 2018 15:13
@codecov-io

codecov-io commented Jul 23, 2018 •

Copy link
Copy Markdown

Codecov Report

Merging #11133 into master will increase coverage by 0.02%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master   #11133      +/-   ##
==========================================
+ Coverage    85.7%   85.73%   +0.02%     
==========================================
  Files         327      327              
  Lines       81995    82139     +144     
==========================================
+ Hits        70274    70418     +144     
  Misses      11721    11721
Impacted Files Coverage Δ
numpy/core/numeric.py 90.96% <ø> (ø) ⬆️
numpy/core/_add_newdocs.py 100% <ø> (ø) ⬆️
numpy/core/setup.py 0% <ø> (ø) ⬆️
numpy/core/tests/test_ufunc.py 98.95% <100%> (+0.1%) ⬆️
numpy/core/tests/test_multiarray.py 98.27% <100%> (+0.01%) ⬆️
numpy/core/tests/test_deprecations.py 91.73% <0%> (-0.11%) ⬇️
numpy/linalg/linalg.py 91.07% <0%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0124839...4254dc0. Read the comment docs.

@tylerjereddy tylerjereddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase on top of the latest master branch so that `.codecov.yml' setting disabling auto-comments can be detected in the PR.

@mattip
mattip force-pushed the matmul-as-ufunc branch from d916028 to 8567f2f Compare July 26, 2018 03:25
@mattip mattip mentioned this pull request Jul 26, 2018
@mattip
mattip force-pushed the matmul-as-ufunc branch from 8567f2f to 4254dc0 Compare July 26, 2018 23:57
@jakirkham

Copy link
Copy Markdown
Contributor

Just noticed there is an optimization for a @ a.T in #6932 and a request for a@ a.H in #6951. Make sure these are not ruined by this refactoring

Thanks for taking these into account. :)

We got a @ a.T to work before by checking whether the data was the same and only striding changed. a @ a.H was a bit hard for us to figure out before given how conjugating will generate a copy.

* out[n, 2] = in1[n, 0]*in2[n, 1] - in1[n, 1]*in2[n, 0]
*/
static void
@TYPE@_cross1d(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may as well just ask--do we have some docs that describe the mechanics of the @ symbol usage in NumPy C source? It appears to be some kind of mechanism for generalization / substitution, but unless I missed a chapter in the C text book, not standard?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numpy uses a custom templating system. The parser is defined in conv_template.py.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I know that now, but it is not well exposed documentation -- I've been working on #11689 & to be fair the distutils docs template section is nothing more than a placeholder.

else
#endif
{
@type@ sum = (@type@)0; /* could make this double */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment here means that there's no compelling advantage to having @type@ of sum be flexible, or something else?

elementary function's implementation.
#. White spaces are ignored.
#. An integer as a dimension name freezes that dimension to the value,
#. The name can be suffixed with a question mark, this make the dimension a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make -> makes I think

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

(void*)op, dm, dn);
} else {
/* cannot reach here */
/* printf("inconceivable to reach this line m=%ld, n=%ld, p=%ld\n",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove debug code and raise some kind of exception here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot error inside a loop. I could just remove it, but it is helpful for reasoning about the code and can be enabled for debugging

/* npy_intp ib1_m = is1_m * dm;
npy_intp ob_m = os_m * dp; */
/*
* TODO: refactor this out to a inner_loop_selector, instead of

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still needs to be done?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but not in this iteration of the code

Comment thread numpy/core/tests/test_multiarray.py Outdated
assert_equal(c, b)
a = np.arange(18).reshape(2, 3, 3)
c = np.matmul(a, a, out=a[::-1, ...])
assert(c.base is a.base)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the switch from assert_ above to plain assert here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

assert_raises(ValueError, umt.cross1d, a, np.arange(4.))
assert_raises(ValueError, umt.cross1d, a, np.arange(3.), np.zeros((3, 4)))

def test_can_ignore_signature(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any way to split this 70-line "unit test" up into smaller pieces logically?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This come from PR #11175 which must be merged before this one. @mhvk any thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, my fault. Might be good to refactor, but out of scope for this PR, I think.

@mattip

mattip commented Oct 19, 2018

Copy link
Copy Markdown
Member Author

Closing this, will reopen as a new, cleaner PR

@mattip mattip closed this Oct 19, 2018
@mattip mattip mentioned this pull request Oct 19, 2018
6 tasks done
@mattip
mattip deleted the matmul-as-ufunc branch March 4, 2019 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants