Skip to content

Add writable args - #7009

Merged
sklam merged 19 commits into
numba:mainfrom
dmbelov:add_writable_args
Mar 28, 2023
Merged

sklam merged 19 commits into
numba:mainfrom
dmbelov:add_writable_args

Conversation

@dmbelov

@dmbelov dmbelov commented May 9, 2021 •

Copy link
Copy Markdown
Contributor

This pull request adds an option writable_args to guvectorize that allows one to specify indices of input arguments that should be read/write. In other words, this PR allows one to write vectorized functions that update arguments in-place with reliability.

Here is simplistic example:

import numba
import numpy as np

def _replace_2nd(x_t, y_1):
    for t in range(0, x_t.size, 2):
        x_t[t] = y_1[0]

replace_2nd = numba.guvectorize(["void(f8[:], f8[:])"], "(t),()", nopython=True)(_replace_2nd)

# replaces every 2nd element by 2 
x_t = np.zeros(5)
replace_2nd(x_t, 2)
assert (x_t[::2] == 2).all()

# does NOT replace every 2nd element by 2 
x_t = np.zeros(5, dtype='f4')
replace_2nd(x_t, 2)
assert (x_t[::2] == 0).all()

# code with writable_args
replace_2nd = numba.guvectorize(["void(f8[:], f8[:])"], "(t),()", nopython=True, writable_args=(0,))(_replace_2nd)

# replace every 2nd element by 2 
x_t = np.zeros(5, dtype='f4')
replace_2nd(x_t, 2)
assert (x_t[::2] == 2).all()

Related issue: #6864

@dmbelov dmbelov changed the title Add writable args [WIP] Add writable args May 9, 2021
@dmbelov dmbelov changed the title [WIP] Add writable args Add writable args May 10, 2021
@gmarkall

Copy link
Copy Markdown
Member

Thanks very much for the PR, I've queued it for review.

@gmarkall gmarkall added this to the Numba 0.55 RC milestone May 10, 2021
@stuartarchibald stuartarchibald added the Effort - long Long size effort needed label Nov 18, 2021
@dmbelov

dmbelov commented Dec 29, 2021

Copy link
Copy Markdown
Contributor Author

Why my PR was moved to Numba 0.56RC. I need this change for my work. Is it possible to return this PR back to Numba 0.55RC?

@gmarkall

Copy link
Copy Markdown
Member

@dmbelov Apologies for the long delay in someone reviewing your PR - it was moved to 0.56 because 0.55 RC was produced before someone could review this.

@gmarkall gmarkall left a comment

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.

Many thanks for the PR - some initial thoughts as I've had a quick look through this.

  • The code changes generally look good and appear to align with the discussions in #6864 and numpy/numpy#18700
  • The documentation presently states that you can't reliably write to input values in this section - https://numba--7009.org.readthedocs.build/en/7009/user/vectorize.html#overwriting-input-values - could you also update this documentation to explain that it is possible using writable_args? Adapting your example from the PR description may also be helpful.
  • Swapping nopython=True for target='cuda' in the decorator results in a TypeError - I think it won't be as simple to support for the CUDA target so you probably won't want to make it work for CUDA; could you ensure that if target='cuda' is used with writable_args, then an explanatory error message is presented to the user please?
  • How does this interact with dynamic gufuncs, where a signature is not provided up front?
  • The PR description mentions that this adds the new kwarg for vectorize as well as guvectorize, but there aren't any tests or example with vectorize - I think it's fine if this is only for guvectorize (I'm not sure I see how it could be applied to vectorize as the arguments to a vectorize function tend to get passed in as scalars) - could you clarify whether there's vectorize support please?
  • I've only skimmed the tests so far - many thanks for adding tests with the PR.

(The error when trying to use the CUDA target was:

  File "/home/gmarkall/numbadev/issues/7009/repro.py", line 8, in <module>
    replace_2nd = numba.guvectorize(["void(f8[:], f8[:])"], "(t),()",)(_replace_2nd)
  File "/home/gmarkall/numbadev/numba/numba/np/ufunc/decorators.py", line 201, in wrap
    guvec = GUVectorize(func, signature, **kwargs)
  File "/home/gmarkall/numbadev/numba/numba/np/ufunc/decorators.py", line 60, in __new__
    return imp(func, signature, identity=identity, cache=cache,
TypeError: __init__() got an unexpected keyword argument 'writable_args'

)

@gmarkall gmarkall added 4 - Waiting on author Waiting for author to respond to review and removed 3 - Ready for Review labels Dec 30, 2021
@sklam sklam modified the milestones: Numba 0.56 RC, PR Backlog Jun 1, 2022
@gmarkall gmarkall added 4 - Waiting on reviewer Waiting for reviewer to respond to author and removed 4 - Waiting on author Waiting for author to respond to review labels Sep 30, 2022
@gmarkall

Copy link
Copy Markdown
Member

@dmbelov Many thanks for the update - I've marked this as waiting for another review.

As a small request regarding the process - could you please avoid force-pushes / rebases and instead add new commits and merge from main please? It is quite hard to tell what changed since the previous review / comments if the history is edited - many thanks in advance.

@gmarkall gmarkall self-assigned this Sep 30, 2022
@gmarkall gmarkall added 4 - Waiting on reviewer Waiting for reviewer to respond to author and removed 4 - Waiting on author Waiting for author to respond to review labels Jan 10, 2023
@gmarkall

Copy link
Copy Markdown
Member

Many thanks for addressing the feedback.

As you know, API for Numpy universal and generalized universal functions does not support keywords.

It doesn't, but I don't think we need to be constrained by what the NumPy API is limited to, if we can provide a broader or easier-to-use API.

Arguments in NumPy C-API are specified by their position. Current interface for writable_args exactly mimics this C-API.

It does - I think it should be possible without too much trouble to allow arguments to be specified by name for writable_args though. I think something like:

diff --git a/numba/np/ufunc/ufuncbuilder.py b/numba/np/ufunc/ufuncbuilder.py
index b50a23d31..8b9dcabf4 100644
--- a/numba/np/ufunc/ufuncbuilder.py
+++ b/numba/np/ufunc/ufuncbuilder.py
@@ -332,7 +332,22 @@ class GUFuncBuilder(_BaseUFuncBuilder):
         self.cache = cache
         self._sigs = []
         self._cres = {}
-        self.writable_args = writable_args
+
+        args = inspect.getfullargspec(py_func).args
+        argpositions = {arg: i for i, arg in enumerate(args)}
+
+        def transform_arg(arg):
+            if isinstance(arg, int):
+                return arg
+
+            try:
+                return argpositions[arg]
+            except KeyError:
+                msg = (f"Specified writable arg {arg} not found in arg list "
+                       f"{args} for function {py_func.__qualname__}")
+                raise RuntimeError(msg)
+
+        self.writable_args = tuple([transform_arg(a) for a in writable_args])
 
     def _finalize_signature(self, cres, args, return_type):
         if not cres.objectmode and cres.signature.return_type != types.void:
diff --git a/numba/tests/npyufunc/test_update_inplace.py b/numba/tests/npyufunc/test_update_inplace.py
index 27cabdc82..dc5a0b4a4 100644
--- a/numba/tests/npyufunc/test_update_inplace.py
+++ b/numba/tests/npyufunc/test_update_inplace.py
@@ -49,6 +49,11 @@ class TestUpdateInplace(TestCase):
                              nopython=True, writable_args=(0,))(py_replace_2nd)
         self._run_test_for_gufunc(gufunc, py_replace_2nd)
 
+        # test with writable_args as strings
+        gufunc = guvectorize(['void(f8[:], f8[:])'], '(t),()',
+                             nopython=True, writable_args=('x_t',))(py_replace_2nd)
+        self._run_test_for_gufunc(gufunc, py_replace_2nd)
+
     def test_update_inplace_with_cache(self):
         # test with writable_args
         gufunc = guvectorize(['void(f8[:], f8[:])'], '(t),()',
@@ -80,6 +85,13 @@ class TestUpdateInplace(TestCase):
                              writable_args=(0, 1, 2))(py_update_3)
         self._run_test_for_gufunc(gufunc, py_update_3)
 
+        # test with writable_args as strings
+        gufunc = guvectorize(['void(f8[:], f8[:], f8[:], f8[:])'],
+                             '(t),(t),(t),()', nopython=True,
+                             writable_args=('x0_t', 'x1_t', 'x2_t'))(py_update_3)
+        self._run_test_for_gufunc(gufunc, py_update_3)
+
+
     def test_exceptions(self):
         # check that len(writable_args) <= nin
         with self.assertRaises(ValueError):

makes it possible. What do you think about the above? If it seems acceptable to you, feel free to use it in this PR - if you do, I am missing a test for the exception being raised when an argument is not found, and a doc update, which would also need to be added.

Can you merge my request to the main branch and put this change in the nearest Numba release?

This PR is in the milestone for the next release, so I do hope we can get it into the next release. I don't feel I can move this to "Ready-to-merge" yet because I'm still concerned about the API, but will also mention this PR in today's triage meeting to get the opinions of other maintainers.

@gmarkall gmarkall added 4 - Waiting on author Waiting for author to respond to review and removed 4 - Waiting on reviewer Waiting for reviewer to respond to author labels Jan 10, 2023
@gmarkall

Copy link
Copy Markdown
Member

The consensus from the dev meeting is that the addition of writable_args should also allow the arguments to be specified by name: draft minutes are at https://hackmd.io/n5qSqeMTR4Wv4UYkUwCZng but they will be added to the wiki over the next couple of days.

@dmbelov

dmbelov commented Jan 15, 2023

Copy link
Copy Markdown
Contributor Author

Thanks. I added changes as suggested. Please check again.

@dmbelov

dmbelov commented Jan 20, 2023

Copy link
Copy Markdown
Contributor Author

@gmarkall, do you think that this PR is ready for merging to the main branch?

@dmbelov

dmbelov commented Jan 30, 2023

Copy link
Copy Markdown
Contributor Author

@gmarkall, I just want to make sure that this PR will be a part of 0.57 release. Is there anything else that needs to be done here before you merge this PR?

@dmbelov

dmbelov commented Feb 10, 2023

Copy link
Copy Markdown
Contributor Author

@gmarkall, any updates?

@gmarkall gmarkall left a comment

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.

Many thanks for the updates @dmbelov. I think the implementation is looking good. Some minor points to resolve:

Many thanks!

Comment thread docs/source/user/vectorize.rst Outdated
Comment thread docs/source/user/vectorize.rst Outdated
Comment thread docs/source/user/vectorize.rst Outdated
@dmbelov

dmbelov commented Feb 25, 2023

Copy link
Copy Markdown
Contributor Author

@gmarkall,
Thanks for the review. I made the changes that you suggested:

  • updated the documentation;
  • added test of the exception raised when the user specifies a non-existent arg by name.

Please check if PR is good to be merged.

@dmbelov
dmbelov requested a review from gmarkall February 26, 2023 02:23
@gmarkall gmarkall added 4 - Waiting on reviewer Waiting for reviewer to respond to author and removed 4 - Waiting on author Waiting for author to respond to review labels Feb 27, 2023

@gmarkall gmarkall left a comment

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.

@dmbelov Many thanks for the updates! I think this looks good.

Before a merge, I'd like this to have a buildfarm run because it contains changes at the C level. @esc / @stuartarchibald could this have a BF run please?

@gmarkall gmarkall added Pending BuildFarm For PRs that have been reviewed but pending a push through our buildfarm 4 - Waiting on CI Review etc done, waiting for CI to finish and removed 4 - Waiting on reviewer Waiting for reviewer to respond to author labels Feb 28, 2023
@dmbelov

dmbelov commented Mar 14, 2023

Copy link
Copy Markdown
Contributor Author

Thanks!

@sklam

sklam commented Mar 28, 2023

Copy link
Copy Markdown
Member

BFID: numba_smoketest_cpu_yaml_171

@sklam

sklam commented Mar 28, 2023

Copy link
Copy Markdown
Member

BFID: numba_smoketest_cpu_yaml_171

passed

@sklam sklam added BuildFarm Passed For PRs that have been through the buildfarm and passed 5 - Ready to merge Review and testing done, is ready to merge and removed Pending BuildFarm For PRs that have been reviewed but pending a push through our buildfarm 4 - Waiting on CI Review etc done, waiting for CI to finish labels Mar 28, 2023
@sklam
sklam merged commit 4f796ad into numba:main Mar 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

5 - Ready to merge Review and testing done, is ready to merge BuildFarm Passed For PRs that have been through the buildfarm and passed Effort - long Long size effort needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants