Skip to content

Invoke a mask callable exactly once - #102

Open
cemde wants to merge 1 commit into
liukidar:mainfrom
cemde:fix/78-process-mask-double-call
Open

Invoke a mask callable exactly once#102
cemde wants to merge 1 commit into
liukidar:mainfrom
cemde:fix/78-process-mask-double-call

Conversation

@cemde

@cemde cemde commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Bug

_process_mask discovered whether a mask callable accepts the is_pytree hint by calling it and catching the failure:

try:
    return mask(kwarg, is_pytree=True)
except TypeError:
    return mask(kwarg)

except TypeError cannot distinguish "this callable does not accept is_pytree" from "this callable ran and raised TypeError for its own reasons".

Impact

A genuinely broken mask executes twice. The user sees the second failure with the first chained onto it, and the traceback points at _process_mask rather than at their own code. Any side effect in the callable happens twice.

It also hid real bugs. Reviewing #68 found that a broken _M_not was silently degraded to the one-argument path by this fallback rather than surfacing.

Fix

Ask the signature instead of inferring from a failure:

if "is_pytree" in inspect.signature(mask).parameters:
    return mask(kwarg, is_pytree=True)
else:
    return mask(kwarg)

The mask is now invoked exactly once, and any error it raises propagates from the user's own frame, unchained. inspect was already imported in this file for _repr_function, so the diff adds no dependency.

Why not a narrower except

Telling the arity TypeError apart from a user's own would mean matching on the exception message text, which is wording-dependent across Python versions and no smaller than this.

Cost

inspect.signature runs once per callable mask leaf, not per parameter. Measured: +13 µs on _process_mask, which is 0.24% of one eager training step and inside run-to-run noise. Under pxf.jit, how every tutorial spells the step, it runs once per trace rather than per step. No caching warranted.

Closes #78

`_process_mask` probed a mask callable's arity by calling it with
`is_pytree=True` and retrying on `TypeError`. That cannot distinguish a callable
that does not accept the keyword from one that ran and raised, so a broken mask
executed twice and reported the second failure chained onto the first. Inspect
the signature instead.

Closes liukidar#78
@cemde
cemde force-pushed the fix/78-process-mask-double-call branch from 8b67f7e to 2b7acf4 Compare August 9, 2026 18:23
@cemde
cemde requested a review from liukidar August 9, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

_process_mask re-invokes a mask callable that raised a real TypeError

1 participant