Repository navigation
fix(billing): bill a ufunc on the shape it writes when out= or where= is larger - #288
Open
AndreasHad04 wants to merge 1 commit into
Open
AndreasHad04 wants to merge 1 commit into
AndreasHad04 wants to merge 1 commit into
Conversation
AndreasHad04
force-pushed
the
fix/275-ufunc-out-where-broadcast
branch
from
October 20, 2026 20:24
05f90c6 to
d74cad1
Compare
… is larger (AIcrowd#275) numpy broadcasts a ufunc's operands against out= (and where=) and evaluates the loop over every output element, but the bill followed the operands: exp(x (2048,), out=O (512, 2048)) was billed for 2,048 elements while numpy computed 1,048,576, and matmul(X, X, out=O (256, 128, 128)) was billed as one product instead of 256. The pointwise factories (unary, binary and their multi-output forms) and clip now bill numel(broadcast(operands, out, where)), carrying the operands' symmetry into that frame with broadcast_group, so the call costs what the same op costs on the operands broadcast to the destination. The gufunc contractions routed through _einsum_routed_binary (matmul, vecdot, matvec, vecmat) multiply their cost by the number of batch elements out= adds. A destination or mask of the operands' own shape bills exactly as before, as does an op without a native out= (it computes over its operand and is copied).
AndreasHad04
force-pushed
the
fix/275-ufunc-out-where-broadcast
branch
from
October 20, 2026 21:44
d74cad1 to
09bf498
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #275.
What was wrong
numpy broadcasts a ufunc's operands against
out=(andwhere=) and evaluates the loop over every output element, but the bill followed the operands.exp(x (2048,), out=O (512, 2048))was billed for 2,048 elements while numpy computed 1,048,576; the same held for binary ufuncs, forwhere=masks (with or withoutout=), and formatmul(X (128, 128), X, out=O (256, 128, 128)), which was billed as one product instead of 256.What it bills now
_counted_unary,_counted_binaryand their multi-output formsmodf,frexp,divmod) andclip:numel(broadcast(operands, out, where))cells instead ofnumel(broadcast(operands)). The operands' symmetry is carried into that frame withbroadcast_group, so the call costs exactly what the same op costs on the operands broadcast to the destination's shape._einsum_routed_binary(matmul,vecdot,matvec,vecmat): the contraction cost timesnumel(out) / numel(result without out=), i.e. once per batch element the destination adds. This scales mu and alpha alike, so the complex-factor override is unchanged. Non-ufunc ops such asdotrefuse a larger destination and are not touched.A destination or mask of the operands' own shape,
where=True, and an op without a nativeout=(it computes over its operand and is copied) bill exactly as before. The cost-model docs now say thatnumel(output)is the shape numpy writes, and note the batch rule under the contraction table.Tests
New
tests/test_out_where_broadcast_billing.py(12 tests), using the issue's shapes:expandmultiplywith a(512, 2048)out=,expwith a(512, 2048)where=mask with and withoutout=,matmulwith a(256, 128, 128)out=,vecdotwith a batchedout=, andmodf/divmod/clipwith larger destinations. Five more covervecdotwithkeepdims=True(with and withoutaxis=) andmatvec/vecmatwith anaxes=that moves the result's core axis: the batch axes a largerout=adds are counted wherever numpy puts that axis. Each asserts the bill equals the same op on the operands materialised at the destination's shape (and, where applicable, the batch count times the plain call), with identical values. A control test checks that same-shape destinations, same-shape masks,where=True, anout=matching a broadcast binary output and a same-shapematmuldestination are unchanged. 11 of the 12 fail without the source change.Results
uv run pytest): 10714 passed, 125 skipped, 2 xfailed.tests/parityfast gate: 135 passed.ruff check,ruff format --check,pyrighton the changed files: clean.