Skip to content

MAINT: refactor ufunc iter operand flags handling - #11580

Merged
mattip merged 1 commit into
numpy:masterfrom
mattip:ufunc-flag-refactor
Nov 6, 2018
Merged

mattip merged 1 commit into
numpy:masterfrom
mattip:ufunc-flag-refactor

Conversation

@mattip

@mattip mattip commented Jul 17, 2018

Copy link
Copy Markdown
Member

In order for the inner loop of matmul to use BLAS, we need to be able to require contiguous input and output memory. Additionally, if there is memory overlap between the output and the input we must not write the results to the output array until looping is finished. linalg solves this problem by copying in the inner loop, which solves the first problem but not the second. einsum and dot solve this problem by not going through PyUFunc_GenericFunction, i.e. they are not true gufuncs.

I can use a custom ufunc->type_resolver function for matmul to copy the inputs to contiguous memory and create an output array with writeback semantics, but there is nowhere to trigger resolution of those semantics after iterating. The most convenient thing would be to use iter operand flags to get the NpyIter to manage the writeback semantics for me. However, the NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE defeats that strategy, it tells NpyIter to avoid creating a writeback buffer under conditions that matmul requires a writeback.

I tried one approach to solving this in PR #11381, which simply removed the NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE flag from generalized ufuncs (ones that call PyUFunc_GeneralizedFunction). It seems people count on that behaviour, and so the PR was rejected.

This is an attempt at another approach, where the handling of the flags is pushed into the ufunc->type_resolver function, and I can override default behaviour in the matmul-specific one that anyhow I need in order to force input arrays to be contiguous.

In this first step, I create a generic function to set per-operand flags and use it in the three looping strategies. In later steps I will try to move the function into the generic ufunc->type_resolver.

@mhvk

mhvk commented Jul 17, 2018

Copy link
Copy Markdown
Contributor

@mattip - I only had a glancing look so far, but I like this as a refactoring, even independently of the final goal. Was the idea to do this over multiple PRs, or over multiple commits? I think the former may be better (at least for this one), so ping me when this is no longer a WIP.

@mattip mattip changed the title WIP: MAINT: refactor ufunc iter operand flags handling MAINT: refactor ufunc iter operand flags handling Jul 22, 2018
@mattip

mattip commented Jul 22, 2018

Copy link
Copy Markdown
Member Author

@mhvk I have gone about as far as I can go with this refactoring. I am not sure where in the code the calls to the new _ufunc_setup_flags function should be placed, I pushed the calls up to be as early as possible just to prove that the calls work and nothing else overrides the flags.

Note that the function itself changed flag handling logic so that if ufunc->op_flags[i] is set, the call will not override any of the flags. Previously output ufunc->op_flags were never read, and input flags were ored together. This is to allow the behaviour I need for matmul-as-a-ufunc (PR #11381 and the description above)

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

@mattip - The changes look good but I am a bit uncomfortable with the changes to the input flags (outputs are OK, since they were never used anyway). See longer note.

cc @eric-wieser as I think it would be good to have another look.

Comment thread numpy/core/src/umath/ufunc_object.c Outdated

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.

Shouldn't the types of op_in_flags, op_out_flags, and op_flags all be the same (i.e., all npy_uint32)? Otherwise, the assignment can be problematic.

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.

fixing

Comment thread numpy/core/src/umath/ufunc_object.c Outdated

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.

If op_in_flags is meant to be the default, why are we adding flags to it? I guess these are always needed? But then, shouldn't they always be added to ufunc->op_flags[i] as well? If the flags are not always needed, I think it may be better to just pass those defaults into the function.

In either case, I think the loop (both for in and out) itself should be:

op_flags[i] = ufunc->op_flags[i] ? ufunc->op_flags[i] : op_in_flags;

with then possibly the or'ing with the "standard" flags after it if they are always needed.

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.

As I stated in the justification for the pull request: there is curently no option for user-defined flags to override the flags here, and I definitely want to exclude NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE. I could also imagine a user who would want to exclude NPY_ITER_ALIGNED if their inner loop does not depend on element access being aligned. I have no strong opinion on whether the op_in_flags should be only the exceptions from the common set of flags or should be orred with a NPY_UFUNC_DEFAULT_OP_FLAGS = NPY_ITER_READONLY | NPY_ITER_ALIGNED | NPY_ITER_OVERLAP_ASSUMED_ELEMENTWISE

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 guess the question is whether there are people out there relying on the flags they define being OR'd with the default ones; we ideally do not break their code.

Comment thread numpy/core/src/umath/ufunc_object.c Outdated

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.

For the outputs, since previously their op_flags were ignored, a change in behaviour would seem OK.

@codecov-io

codecov-io commented Jul 25, 2018 •

Copy link
Copy Markdown

Codecov Report

Merging #11580 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master   #11580   +/-   ##
=======================================
  Coverage    85.7%    85.7%           
=======================================
  Files         327      327           
  Lines       81985    81985           
=======================================
  Hits        70264    70264           
  Misses      11721    11721

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 210f07f...7480760. Read the comment docs.

@mattip
mattip force-pushed the ufunc-flag-refactor branch from e7eb245 to 7480760 Compare July 25, 2018 17:07
@mattip

mattip commented Jul 25, 2018

Copy link
Copy Markdown
Member Author

Rebased off master to reduce codecov noise, squashed commits, removed possibly backward-incompatible behavior with input flags

@mattip

mattip commented Jul 25, 2018

Copy link
Copy Markdown
Member Author

@mhvk I left the input flag logic as it was previously

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

With this, I think we're safe and can discuss the question of whether the logic for inputs should change separately.

I'm approving, but will wait a day before merging to give @eric-wieser a chance to chime in.

@mattip

mattip commented Aug 6, 2018

Copy link
Copy Markdown
Member Author

@eric-wieser any thoughts?

Comment thread numpy/core/src/umath/ufunc_object.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lines < 80 characters.

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

Comment thread numpy/core/src/umath/ufunc_object.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Lines too long. also below.

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

@charris

charris commented Aug 15, 2018

Copy link
Copy Markdown
Member

Needs some style fixes to break up the long lines.

Comment thread numpy/core/src/umath/ufunc_object.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

typo

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.

reworked entirely

Comment thread numpy/core/src/umath/ufunc_object.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This could maybe do with a docstring

Comment thread numpy/core/src/umath/ufunc_object.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are op_flags and ufunc->op_flags here ever the same pointer?

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.

No. op_flags is the per-call copy of ufunc->op_flags, updated for the actual call arguments and variation (reduce, __call__, reduceat)

@eric-wieser

Copy link
Copy Markdown
Member

linalg solves this problem by copying in the inner loop, which solves the first problem but not the second

Does this second problem have a visible effect in linalg?

Comment thread numpy/core/src/umath/ufunc_object.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Docstring for this function should be updated with this new argument

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.

Reworked

@mattip

mattip commented Aug 21, 2018

Copy link
Copy Markdown
Member Author

Does this second problem have a visible effect in linalg?

The second problem was overlap between input and output, which means we need to use writeback semantics or another technique to prevent writing to input. As far as I can tell, the problem does not exist in linalg functions in the first place, correct me if I am wrong.

@mattip

mattip commented Sep 9, 2018

Copy link
Copy Markdown
Member Author

ping

@mattip

mattip commented Oct 19, 2018

Copy link
Copy Markdown
Member Author

@eric-wieser can we merge this?

@mattip mattip mentioned this pull request Oct 19, 2018
6 tasks done
@eric-wieser eric-wieser self-assigned this Oct 19, 2018
@mattip
mattip force-pushed the ufunc-flag-refactor branch from 37d8f1b to 9afa2f7 Compare October 21, 2018 05:51
@mattip

mattip commented Oct 27, 2018

Copy link
Copy Markdown
Member Author

@eric-wieser can we merge this PR? It is blocking further progress with PR #11133 matmul-as-ufunc

@stefanv

stefanv commented Oct 31, 2018

Copy link
Copy Markdown
Contributor

Gentle ping to see if we can get this one closed down.

@mattip

mattip commented Nov 6, 2018

Copy link
Copy Markdown
Member Author

Merging. @eric-wieser if you have further comments please raise them in a new issue

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.

6 participants