Skip to content

Invalidations 2/6: fuse nested broadcasts without broadcastable - #339

Draft
asinghvi17 wants to merge 2 commits into
inv/small-fixesfrom
inv/broadcast-unwrap
Draft

asinghvi17 wants to merge 2 commits into
inv/small-fixesfrom
inv/broadcast-unwrap

Conversation

@asinghvi17

@asinghvi17 asinghvi17 commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Part 2/6, on #338.

Removes Base.broadcastable(::BroadcastDiskArray). Base calls broadcastable on every broadcast argument, so this method invalidated compiled broadcast code. Nested lazy broadcasts still fuse: DiskArrays' own copy/copyto! now unwrap them with a new internal DiskArrays.unwrap_broadcast. A backend with its own broadcast style should call it too (docs/src/backend.md).

Values, reads and allocations match #316, and a new test guards the fusion.

uinvalidated after using DiskArrays (#338 → this PR):

session Julia 1.13 Julia 1.12
clean 153 → 153 503 → 503
CairoMakie session 2826 → 2826 3273 → 3238
many packages loaded 1785 → 1785 1915 → 1845
DataFrames session 807 → 807 1524 → 1506

On 1.13 this method forms no tree, so only the 1.12 column moves.

🤖 Generated with Claude Code

https://claude.ai/code/session_019Dfq6aDr3GqA8w61TsvMsc

asinghvi17 and others added 2 commits October 8, 2026 00:08
Remove Base.broadcastable(::BroadcastDiskArray). It replaced a lazy disk
broadcast by its Broadcasted wherever it was broadcast again, so flatten could
fuse the expression, but a method on broadcastable invalidates compiled
broadcast code that calls broadcastable on abstractly inferred arguments.

DiskArrays now does the same unwrapping inside its own ChunkStyle copy/copyto!
methods (new internal helper unwrap_broadcast), and before handing a
Broadcasted{Nothing} to the diskarrays_coptyo! backend hook. Values, chunk
reads and allocations of nested broadcasts are unchanged.

Backends that return their own style from diskarrays_broadcaststyle can call
unwrap_broadcast in their copy/copyto! methods; document this in backend.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Dfq6aDr3GqA8w61TsvMsc
Check that s ./ a3, with s = a1 .+ a2 .* 2 lazy, has the leaf arrays as
arguments, gives the same values, reads each chunk of each leaf once, and
allocates no more than the same expression written at once. Also check
unwrap_broadcast with a foreign style and the diskarrays_coptyo! hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Dfq6aDr3GqA8w61TsvMsc
@asinghvi17
asinghvi17 added this pull request to stack #342 October 8, 2026 14:03
@asinghvi17 asinghvi17 changed the title inv/broadcast unwrap Invalidations 2/4: fuse nested lazy broadcasts without a broadcastable method Oct 8, 2026
@asinghvi17 asinghvi17 changed the title Invalidations 2/4: fuse nested lazy broadcasts without a broadcastable method Invalidations 2/4: fuse nested broadcasts without broadcastable Oct 8, 2026
@asinghvi17 asinghvi17 changed the title Invalidations 2/4: fuse nested broadcasts without broadcastable Invalidations 2/6: fuse nested broadcasts without broadcastable Oct 8, 2026

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

Looks good to me. I checked out the branch: the full suite passes on Julia 1.13.

Verification. I ran 14 broadcast cases on #338 and on this PR and compared them: s .+ s, 3-deep chains, vector/row/Ref operands, ifelse. mixes, sum(s .* 2), indexing/view/adjoint of a lazy broadcast, and dest .= s .* b into an Array and into a disk array. Results are identical in every case, including the number of reads of the leaf array (2 for a shared subexpression, both before and after). Base.broadcastable(s) === s is now true, as intended.

Comments

  1. Broadcast.flatten is a visible change. For Broadcast.broadcasted(+, s, 1) with a lazy s, flatten(bc).args had 3 entries before and has 2 now (the lazy array is an opaque leaf). Nothing is wrong inside DiskArrays, and backend.md documents unwrap_broadcast for backends. Still, any downstream package that calls flatten on user expressions (not only backends with their own style) loses fusion until it calls unwrap_broadcast. A CHANGELOG entry saying so would help (there is none in this PR).
  2. Add a direct guard for the invalidation. The new tests pin the fusion behavior, and they would also pass with Base.broadcastable(::BroadcastDiskArray) put back, so nothing stops the invalidating method from returning. Something like @test !any(m -> m.module === DiskArrays, methods(Base.broadcastable)) would. I use the same pattern for Base.Generator in #345.
  3. Brittle assertions. length(bc.args) == 4 in isfused depends on the exact shape flatten produces, and @allocated nested <= @allocated direct can differ across Julia versions (nightly) or with inlining. Fine today; if CI flakes, those are the first suspects.
  4. As the description says, on 1.13 this method forms no invalidation tree (my 1.13 numbers for #338 → #339 are unchanged), so the benefit is on 1.12. That is a reasonable justification given there is no behavior change.

Not blocking.

This branch has not been deployed

No deployments
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.

2 participants