Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Fix non-inplace IfElse on numba mode#1765

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
emekaokoli19 wants to merge3 commits intopymc-devs:main
base:main
Choose a base branch
Loading
fromemekaokoli19:fix/numba-ifelse-copy-inputs

Conversation

@emekaokoli19
Copy link
Contributor

Description

This PR fixes an issue in theIfElse numba, where outputs were returned as direct references to the input arrays instead of copies. This violated the semantics ofifelse, which guarantees that the returned value is a distinct object, even when both branches reference the same input.

The fix ensures that each selected output is explicitly copied. This matches the behavior of the Python linker and prevents unexpected mutations when the NumPy arrays are assumed to be non-shared.

Related Issue

Checklist

Type of change

  • New feature / enhancement
  • Bug fix
  • Documentation
  • Maintenance
  • Other (please specify):

Copy link
Member

@ricardoV94ricardoV94 left a comment

Choose a reason for hiding this comment

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

Nice trick with the list, I was thinking we would need to use codegen due to numba limitations.

Left some comments.

We also need to work on the existing tests so they would have failed before the fix and pass now. We have to test both single and multi output and inplace or not

# Return a tuple of copies
out= [None]*n_outs
foriinrange(n_outs):
out[i]=selected[i].copy()
Copy link
Member

Choose a reason for hiding this comment

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

We should only make a copy if not op.inplace

defifelse(cond,*args):
ifcond:
res=args[:n_outs]
arr=args[0]
Copy link
Member

@ricardoV94ricardoV94Dec 3, 2025
edited
Loading

Choose a reason for hiding this comment

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

This case can be simplified to have signatureifelse(cond, if_true, if_false), without need for indexing internally.

It's unrelated to the copy change

# Return a copy
returnarr.copy()

returnifelse
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
returnifelse
cache_version=1
returnifelse,cache_version

We need to tell PyTensor the implementation of ifelse changed to invalidate old caches

@ricardoV94
Copy link
Member

The list trick didn't work. I suspect that would happen.

We need to use codegen for the non inplace version of ifelse with multiple outputs. Other cases will work fine.

You can see some cases of codegen for the numba funcifyAlloc orSpecifyShape that may give some ideas. This tends to happen everytime we would want (*args) in the signature of a numba func

@ricardoV94ricardoV94 changed the titlefix-make copies of inputs in numbaFix non-inplace IfElse on numba modeDec 3, 2025
@emekaokoli19
Copy link
ContributorAuthor

The list trick didn't work. I suspect that would happen.

We need to use codegen for the non inplace version of ifelse with multiple outputs. Other cases will work fine.

You can see some cases of codegen for the numba funcifyAlloc orSpecifyShape that may give some ideas. This tends to happen everytime we would want (*args) in the signature of a numba func

Hey@ricardoV94, I have added codegen. A lot of tests are failing intests/link/numba, so I wrote some new tests to test theifelse changes. Are the failing tests intests/link/numba a result of something else?

Comment on lines 109 to 114
ifn_outs==1:

@numba_basic.numba_njit
defifelse(cond,*args):
ifcond:
res=args[:n_outs]
else:
res=args[n_outs:]
defifelse(cond,x_true,x_false):
arr=x_trueifcondelsex_false
returnarrifas_viewelsearr.copy()
Copy link
Member

Choose a reason for hiding this comment

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

On second thought I guess we can get rid of the special case and stay with the codegen for every case now

ifelse_numba=numba_basic.numba_njit(ifelse_py)

returnres[0]
cache_version=3
Copy link
Member

Choose a reason for hiding this comment

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

You probably needed to bump a few times, but for the PR we should only bump once. You can erase your previous cache with pytensor-cache purge for local testing

Suggested change
cache_version=3
cache_version=1

assertr2isnotb


deftest_ifelse_false_branch():
Copy link
Member

Choose a reason for hiding this comment

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

You can merge this test with the previous ones. Just eval the function twice, in a way that triggers the different branches.

y=pt.vector("y")
out1,out2=ifelse(x.sum()>0, (x,y), (y,x))

fn=function([x,y], [out1,out2],mode=Mode("numba",optimizer=None))
Copy link
Member

Choose a reason for hiding this comment

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

Parametrize this and the single output test to test inplace and not inplace. You can create IfElse inplace manually likeIfElse(as_view=True|False, n_outs=2), and passaccept_inplace=Truetofunction`.

We want to make sure that r1 is a, r2 is b in that case. Right now we are never testing the inplace mode

@emekaokoli19
Copy link
ContributorAuthor

@ricardoV94

Explanation for failingis checks in Numba mode tests

The tests useassert res is a to check if the output is the same Python object as the input. This fails under Numba and I think it is because:

  1. IfElse withas_view=True normally returns the same object in Python mode.
  2. InNumba mode, the compiled functioncreates new arrays for outputs, even whenas_view=True.
  3. Therefore,res is a fails because object identity is different, even though the arrayvalues are identical.

Should we replaceis checks withnp.array_equal, which testselement-wise equality instead of memory identity? However, this would mean thatas_view would be redundant withnumba mode

@ricardoV94
Copy link
Member

ricardoV94 commentedDec 4, 2025
edited
Loading

Ah you need to set the borrow flags to allow pytensor to pass back inputs unalterated:

importnumpyasnpimportpytensorimportpytensor.tensorasptx=pt.vector("x")fn=pytensor.function([pytensor.In(x,borrow=True)],pytensor.Out(x,borrow=True),mode="NUMBA")x_test=np.zeros(5)fn(x_test)isx_test

You can check that without that the final function would have a deepcopy Op, usingfn.dprint()

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@ricardoV94ricardoV94ricardoV94 requested changes

Requested changes must be addressed to merge this pull request.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Numba ifelse does not copy inputs when not inplace

2 participants

@emekaokoli19@ricardoV94

[8]ページ先頭

©2009-2025 Movatter.jp