Skip to content

Commit 5da084a

Browse files
mtfishmanclaude
andauthored
Lower linear broadcasting through bipermutedimsopadd! (#232)
## Summary Routes linear broadcasting of `AbstractNamedDimsArray`s through TensorAlgebra's `bipermutedimsopadd!`. `copyto!` and `copy` now run the denamed broadcast through `tryflattenlinear`, and the linear case lowers to `permutedimsopadd!` on the name-aligned operands, where each operand's `PermutedDimsArray` alignment is unwrapped down to the backend's own `bipermutedimsopadd!` (the unwrap added in ITensor/TensorAlgebra.jl#178). Nonlinear broadcasts fall back to the existing denamed Base broadcast. For a graded or block-sparse backing, a permuted broadcast now reaches the block-wise primitive and the result keeps its backend type rather than densifying. Out-of-place results allocate from a bare operand prototype with the broadcast result axes, since `similar` of a permuted wrapper drops the backend. Drops the `FunctionImplementations` dependency: `aligneddims` now gets its lazy permute through `TensorAlgebra.permuteddims`, and the now-dead `ImplementationStyle` trait that routed it is removed. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 32fc0ed commit 5da084a

3 files changed

Lines changed: 32 additions & 24 deletions

File tree

Project.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "NamedDimsArrays"
22
uuid = "60cbd0c0-df58-4cb7-918c-6f5607b73fde"
3-
version = "0.15.8"
3+
version = "0.15.9"
44
authors = ["ITensor developers <support@itensor.org> and contributors"]
55

66
[workspace]
@@ -10,7 +10,6 @@ projects = ["benchmark", "dev", "docs", "examples", "test"]
1010
ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"
1111
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
1212
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
13-
FunctionImplementations = "7c7cc465-9c6a-495f-bdd1-f42428e86d0c"
1413
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1514
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
1615
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
@@ -39,13 +38,12 @@ ArrayLayouts = "1.11"
3938
BlockArrays = "1.3"
4039
Compat = "4.16"
4140
FillArrays = "1.13"
42-
FunctionImplementations = "0.4"
4341
LinearAlgebra = "1.10"
4442
Mooncake = "0.4.202, 0.5"
4543
OrderedCollections = "1.6"
4644
Random = "1.10"
4745
SimpleTraits = "0.9.4"
48-
TensorAlgebra = "0.9.5"
46+
TensorAlgebra = "0.9.6"
4947
TupleTools = "1.6"
5048
TypeParameterAccessors = "0.4"
5149
VectorInterface = "0.5, 0.6"

src/abstractnameddimsarray.jl

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using FunctionImplementations: FunctionImplementations as FI
21
using LinearAlgebra: LinearAlgebra
32
using Random: Random
3+
using TensorAlgebra: permuteddims
44
using TypeParameterAccessors: unspecify_type_parameters
55

66
# Some of the interface is inspired by:
@@ -9,20 +9,11 @@ using TypeParameterAccessors: unspecify_type_parameters
99
# https://github.com/mcabbott/NamedPlus.jl
1010
# https://pytorch.org/docs/stable/named_tensor.html
1111

12-
abstract type AbstractNamedDimsArrayImplementationStyle <:
13-
FI.AbstractArrayImplementationStyle end
14-
15-
struct NamedDimsArrayImplementationStyle <: AbstractNamedDimsArrayImplementationStyle end
16-
1712
abstract type AbstractNamedDimsArray{T, N} <: AbstractArray{T, N} end
1813

1914
const AbstractNamedDimsVector{T} = AbstractNamedDimsArray{T, 1}
2015
const AbstractNamedDimsMatrix{T} = AbstractNamedDimsArray{T, 2}
2116

22-
function FI.ImplementationStyle(type::Type{<:AbstractNamedDimsArray})
23-
return NamedDimsArrayImplementationStyle()
24-
end
25-
2617
dimnames(a::AbstractNamedDimsArray) = throw(MethodError(dimnames, a))
2718
function dimnames(a::AbstractNamedDimsArray, dim::Int)
2819
return dimnames(a)[dim]
@@ -54,13 +45,7 @@ dimnametype(type::Type{<:AbstractNamedDimsArray}) = throw(MethodError(dimnametyp
5445
# Unwrapping the names (`NamedDimsArrays.jl` interface).
5546
# TODO: Use `IsNamed` trait?
5647
denamed(a::AbstractNamedDimsArray) = throw(MethodError(denamed, a))
57-
function denamed(a::AbstractNamedDimsArray, inds)
58-
# Skip the lazy `PermutedDimsArray` wrap when the requested order already
59-
# matches `a`'s; compare via `Tuple` because `LittleSet ==` is
60-
# set-equality and would mask a permutation.
61-
Tuple(name.(inds)) == Tuple(dimnames(a)) && return denamed(a)
62-
return denamed(aligneddims(a, inds))
63-
end
48+
denamed(a::AbstractNamedDimsArray, inds) = denamed(aligneddims(a, inds))
6449
dename(a::AbstractNamedDimsArray, inds) = denamed(aligndims(a, inds))
6550

6651
# Output the named axes/indices of the named dims array.
@@ -706,7 +691,7 @@ function aligneddims(a::AbstractArray, dims)
706691
)
707692
)
708693
return nameddimsconstructorof(a)(
709-
FI.permuteddims(denamed(a), perm), new_dimnames
694+
permuteddims(denamed(a), perm), new_dimnames
710695
)
711696
end
712697

src/broadcast.jl

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ function broadcasted_denamed(bc::Broadcasted, inds)
100100
return broadcasted(bc.f, Base.Fix2(broadcasted_denamed, inds).(bc.args)...)
101101
end
102102

103+
# A bare (unnamed) array operand, used as an allocation prototype so a broadcast
104+
# result inherits the operands' backend (e.g. graded) rather than a lazy permuted
105+
# wrapper's `similar` (which can drop the backend).
106+
denamed_prototype(bc::Broadcasted) = denamed_prototype(bc.args...)
107+
denamed_prototype(arg::AbstractNamedDimsArray, args...) = denamed(arg)
108+
denamed_prototype(arg::Broadcasted, args...) = denamed_prototype(arg.args..., args...)
109+
denamed_prototype(arg, args...) = denamed_prototype(args...)
110+
103111
function Base.similar(bc::Broadcasted{<:AbstractNamedDimsArrayStyle}, elt::Type, ax)
104112
inds_a = name.(ax)
105113
bc_denamed = broadcasted_denamed(bc, inds_a)
@@ -120,14 +128,31 @@ function Base.copy(bc::Broadcasted{<:AbstractNamedDimsArrayStyle})
120128
# the output element type at runtime with widening.
121129
inds_dest = inds(bc)
122130
bc_denamed = broadcasted_denamed(bc, inds_dest)
123-
dest_denamed = copy(bc_denamed)
131+
lb = TA.tryflattenlinear(bc_denamed)
132+
if isnothing(lb)
133+
# Not a linear combination: ordinary fused broadcast.
134+
dest_denamed = copy(bc_denamed)
135+
else
136+
# Linear: lower to bipermutedimsopadd!. Allocate from an operand so the
137+
# result keeps the backend, using the backend's result axes (not `lb`'s).
138+
dest_axes = denamed.(Tuple(axes(bc)))
139+
dest_denamed = similar(denamed_prototype(bc), eltype(lb), dest_axes)
140+
copyto!(dest_denamed, lb)
141+
end
124142
return nameddimstype(bc.style)(dest_denamed, inds_dest)
125143
end
126144

127145
function Base.copyto!(dest::AbstractArray, bc::Broadcasted{<:AbstractNamedDimsArrayStyle})
128146
dest_denamed = denamed(dest)
129147
inds_dest = inds(dest)
130148
bc_denamed = broadcasted_denamed(bc, inds_dest)
131-
copyto!(dest_denamed, bc_denamed)
149+
lb = TA.tryflattenlinear(bc_denamed)
150+
if isnothing(lb)
151+
# Not a linear combination: ordinary fused broadcast.
152+
copyto!(dest_denamed, bc_denamed)
153+
else
154+
# Linear: lower to bipermutedimsopadd! into the existing dest.
155+
copyto!(dest_denamed, lb)
156+
end
132157
return dest
133158
end

0 commit comments

Comments
 (0)