Skip to content

Commit 10c18da

Browse files
authored
Merge pull request #566 from ValeevGroup/evaleev/feature/einsum-expr-nesting-preserving
einsum: delegate nesting-preserving products to the expression layer; drop per-tile reference kernels
2 parents 355f9c8 + 5e8d5b4 commit 10c18da

15 files changed

Lines changed: 269 additions & 307316 deletions

bin/generate_answers.ipynb

Lines changed: 0 additions & 756 deletions
This file was deleted.

src/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ TiledArray/expressions/binary_expr.h
110110
TiledArray/expressions/blk_tsr_engine.h
111111
TiledArray/expressions/blk_tsr_expr.h
112112
TiledArray/expressions/cont_engine.h
113-
TiledArray/expressions/contraction_helpers.h
114113
TiledArray/expressions/expr.h
115114
TiledArray/expressions/expr_engine.h
116115
TiledArray/expressions/expr_trace.h

src/TiledArray/einsum/tiledarray.h

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -673,23 +673,38 @@ auto einsum(expressions::TsrExpr<ArrayA_> A, expressions::TsrExpr<ArrayB_> B,
673673
auto range_map =
674674
(RangeMap(a, A.array().trange()) | RangeMap(b, B.array().trange()));
675675

676-
// special Hadamard
676+
// Fused broadcast: one operand is ENTIRELY fused (h == its index set) and
677+
// there is no contraction -- C(h,e) = A * B, a per-fused-block scale. The
678+
// general-product expression engine evaluates this natively when the
679+
// fully-fused operand LEADS (it folds to a rank-0 left tile, restored by a
680+
// synthetic unit left-external mode; see ContEngine::
681+
// synthetic_unit_left_external). Order the fully-fused operand first and
682+
// delegate -- no physical replication needed.
677683
if (h.size() == a.size() || h.size() == b.size()) {
678684
TA_ASSERT(!i && e);
679-
bool const small_a = h.size() == a.size();
680-
auto const delta_trng = make_trange(range_map, e);
685+
_ein_call.branch = "fused-broadcast-expression";
686+
const bool a_leads = (h.size() == a.size()); // A entirely fused?
687+
// the engine produces the result in its canonical inner layout
688+
// (inner fused, then left-then-right inner externals, in the order the
689+
// operands are handed to it) and rejects a non-identity inner result
690+
// permutation; evaluate canonically, then apply the inner permutation
691+
// (cf. the generalized-inner-perm-recurse path below). The canonical
692+
// inner-external order tracks the operand order, so mirror the swap.
693+
std::string canon_inner;
694+
if constexpr (IsArrayToT<ArrayC>) {
695+
auto inner_e = a_leads ? (inner.A ^ inner.B) : (inner.B ^ inner.A);
696+
canon_inner = ";" + (std::string)(inner.h + inner_e);
697+
}
698+
std::string canon_layout = std::string(c) + canon_inner;
699+
ArrayC C0;
700+
if (a_leads) // A fully fused -> already leads
701+
C0(canon_layout) = A * B;
702+
else // B fully fused -> put it first (multiplication commutes)
703+
C0(canon_layout) = B * A;
681704
std::string target_layout = std::string(c) + inner.c;
705+
if (target_layout == canon_layout) return C0;
682706
ArrayC C;
683-
if (small_a) {
684-
auto temp = replicate_array(A.array(), delta_trng);
685-
std::string temp_layout = std::string(e) + "," + A.annotation();
686-
C(target_layout) = temp(temp_layout) * B;
687-
} else {
688-
auto temp = replicate_array(B.array(), delta_trng);
689-
std::string temp_layout = std::string(e) + "," + B.annotation();
690-
C(target_layout) = A * temp(temp_layout);
691-
}
692-
707+
C(target_layout) = C0(canon_layout); // inner-permute to requested layout
693708
return C;
694709
}
695710

@@ -791,7 +806,28 @@ auto einsum(expressions::TsrExpr<ArrayA_> A, expressions::TsrExpr<ArrayB_> B,
791806
}
792807
}
793808

794-
if (!e) { // hadamard reduction
809+
if (!e) { // no external outer index (fused + contracted): a nesting-
810+
// preserving reduction. By default the expression layer handles
811+
// it natively (synthetic unit left-external mode, see
812+
// ContEngine::synthetic_unit_left_external); the legacy local
813+
// kernel below is retained only as the opt-in legacy
814+
// cross-check oracle (cf. the generalized-subworld path).
815+
if (!detail::einsum_legacy_subworld()) {
816+
_ein_call.branch = "no-external-expression";
817+
// evaluate in the engine's canonical inner layout, then apply any
818+
// inner result permutation (cf. the broadcast / inner-perm-recurse)
819+
std::string canon_inner;
820+
if constexpr (IsArrayToT<ArrayC>)
821+
canon_inner = ";" + (std::string)(inner.h + inner.e);
822+
std::string canon_layout = std::string(c) + canon_inner;
823+
ArrayC C0;
824+
C0(canon_layout) = A * B;
825+
std::string target_layout = std::string(c) + inner.c;
826+
if (target_layout == canon_layout) return C0;
827+
ArrayC C;
828+
C(target_layout) = C0(canon_layout);
829+
return C;
830+
}
795831

796832
_ein_call.branch = "hadamard-reduction-local";
797833
const auto _ein_he_t0 = _ein_call.active ? now() : time_point{};

src/TiledArray/expressions/cont_engine.h

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -677,18 +677,20 @@ class ContEngine : public BinaryEngine<Derived> {
677677
this->init_perm(target_indices);
678678
general_repermute_ = (outer(target_indices) != outer(indices_));
679679

680-
// A product with NO external (free) outer indices (every outer index
681-
// fused or contracted, e.g. C("i,j;a,b") = A("x,i,j;a") * B("x,i,j;b"))
682-
// folds to a GEMM with no free modes, i.e. rank-0 tensors, which the
683-
// tile kernels do not support. Evaluate it with a SYNTHETIC UNIT
684-
// left-external mode instead: the folded product becomes
685-
// (1,K) x (K) -> (1), the exact shape of the (supported) one-sided
686-
// neB == 0 case. The unit mode lives only in the tile op's GemmHelper;
687-
// tranges, shapes and tiles carry the true (external-free) ranks, and
688-
// BatchedContractReduce / SparseShape::gemm_batched detect the
689-
// synthetic mode from the one-rank mismatch and pad their folded views
680+
// Some degenerate folded shapes would carry a rank-0 tensor, which the
681+
// tile kernels do not support (see synthetic_unit_left_external()):
682+
// - a NO-EXTERNAL product (every outer index fused or contracted, e.g.
683+
// C("i,j;a,b") = A("x,i,j;a") * B("x,i,j;b")) folds to a rank-0 RESULT;
684+
// - a FUSED BROADCAST (the left operand is entirely fused, no contraction,
685+
// e.g. C("b,k") = A("b") * B("b,k")) folds to a rank-0 LEFT operand.
686+
// Evaluate both with a SYNTHETIC UNIT left-external mode: the folded
687+
// product becomes (1,K) x (K,N) -> (1,N), a supported shape (the no-
688+
// external case has N == 1, the broadcast has K == 1). The unit mode lives
689+
// only in the tile op's GemmHelper; tranges, shapes and tiles carry the
690+
// true ranks, and BatchedContractReduce / SparseShape::gemm_batched detect
691+
// the synthetic mode from the one-rank mismatch and pad their folded views
690692
// with a unit extent.
691-
const unsigned int u = (outer_size(indices_) == nh) ? 1u : 0u;
693+
const unsigned int u = synthetic_unit_left_external();
692694

693695
// the tile op operates on the folded (fused-mode-free) shapes; the
694696
// synthetic unit mode leads the folded left operand, so it is NoTrans
@@ -749,17 +751,34 @@ class ContEngine : public BinaryEngine<Derived> {
749751
}
750752
}
751753

754+
/// \return 1 if the folded general product needs a SYNTHETIC unit
755+
/// left-external mode, else 0. The folded (fused-mode-free) GEMM cannot host
756+
/// a rank-0 tensor, which arises in two degenerate cases:
757+
/// - rank-0 RESULT: every outer index is fused or contracted (no external),
758+
/// i.e. outer_size(indices_) == n_fused_modes_;
759+
/// - rank-0 LEFT operand: the left argument is entirely fused with no
760+
/// contraction (a fused broadcast / per-fused-block scale), i.e.
761+
/// outer_size(left_indices_) == n_fused_modes_.
762+
/// A unit left-external mode (carried only in the GemmHelper) restores a
763+
/// supported (1,K) x (K,N) -> (1,N) shape in both cases.
764+
unsigned int synthetic_unit_left_external() const {
765+
return (outer_size(indices_) == n_fused_modes_ ||
766+
outer_size(left_indices_) == n_fused_modes_)
767+
? 1u
768+
: 0u;
769+
}
770+
752771
/// Tiled range factory function for a general product
753772

754773
/// \return The result tiled range: the fused mode ranges followed by the
755774
/// left- and right-external mode ranges
756775
trange_type make_trange_general() const {
757776
const unsigned int nh = n_fused_modes_;
758777
const unsigned int nc = op_.gemm_helper().num_contract_ranks();
759-
// the no-external case carries a synthetic unit left-external mode in
760-
// the GemmHelper only (see init_struct_general); the actual tranges do
761-
// not have it
762-
const unsigned int u = (outer_size(indices_) == n_fused_modes_) ? 1u : 0u;
778+
// degenerate folds carry a synthetic unit left-external mode in the
779+
// GemmHelper only (see synthetic_unit_left_external()); the actual tranges
780+
// do not have it
781+
const unsigned int u = synthetic_unit_left_external();
763782
const unsigned int neA = op_.gemm_helper().left_rank() - nc - u;
764783
const unsigned int neB = op_.gemm_helper().right_rank() - nc;
765784

@@ -812,10 +831,10 @@ class ContEngine : public BinaryEngine<Derived> {
812831
std::shared_ptr<const pmap_interface> pmap) {
813832
const unsigned int nh = n_fused_modes_;
814833
const unsigned int nc = op_.gemm_helper().num_contract_ranks();
815-
// the no-external case carries a synthetic unit left-external mode in
816-
// the GemmHelper only (see init_struct_general); the actual tranges do
817-
// not have it
818-
const unsigned int u = (outer_size(indices_) == nh) ? 1u : 0u;
834+
// degenerate folds carry a synthetic unit left-external mode in the
835+
// GemmHelper only (see synthetic_unit_left_external()); the actual tranges
836+
// do not have it
837+
const unsigned int u = synthetic_unit_left_external();
819838
const unsigned int neA = op_.gemm_helper().left_rank() - nc - u;
820839
const unsigned int neB = op_.gemm_helper().right_rank() - nc;
821840

0 commit comments

Comments
 (0)