1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//! Batched dot products, as gemm rather than broadcast-multiply-then-sum.
//!
//! # Why this module exists
//!
//! `Σ_h a[b,k,h]·v[b,h]` is naturally written `a.broadcast_mul(&v.unsqueeze(1)?)?.sum(2)`,
//! and that form is slow twice over:
//!
//! - **The multiply.** Broadcasting `[B,1,H]` against `[B,K,H]` puts a stride-0
//! dim BETWEEN two non-zero strides. `candle_core::Layout::offsets_b` strips
//! only LEADING and TRAILING zero strides before requiring the rest to be
//! contiguous, so that layout returns `None` and the op falls to a scalar
//! `StridedIndex` loop — no SIMD, single-threaded.
//! - **The reduction.** `sum` takes candle's vectorized path only when it
//! reduces over the LAST dims. Reducing over a middle axis walks every element
//! doing an integer div/mod and a scattered accumulate.
//!
//! Either way an `[B,K,H]` product is materialized, and backward turns that one
//! temporary into several more. `matmul` keeps the product implicit and hits
//! gemm. Measured on this workspace: 10–26x on realistic shapes, and still 4–5x
//! at the smallest shapes tested — there is no size at which the broadcast form
//! wins, so these are unconditional replacements.
//!
//! Both helpers live here rather than in a model type because the callers span
//! `legume_numeric::candle`, `graph-embedding-util` and `pinto`, and every one of them can
//! reach this crate.
//!
//! `legume_numeric::matrix` would serve those callers too — it is a strict dependency-order
//! improvement, and `crate::matrix::traits::FusedTensorOps` is a candle perf helper
//! that lives there. The split is by what the helper does, not by what it can
//! reach: these two REPLACE a shape with a better one, so they belong beside the
//! `Tensor`-shaped model code that picks shapes. A fused elementwise kernel keeps
//! the shape and only changes how the elements are walked, which is
//! `legume_numeric::matrix`'s business, next to its other `impl … for Tensor` blocks.
use ;
/// `out[b,k] = Σ_h a[b,k,h] · v[b,h]` — a per-batch mat-vec.
///
/// `a` is `[B, K, H]`, `v` is `[B, H]`, result is `[B, K]`.
/// `out[b,h] = Σ_k w[b,k] · a[b,k,h]` — the transposed case, i.e. a weighted sum
/// over `K` (attention pooling, mixture collapse).
///
/// `w` is `[B, K]`, `a` is `[B, K, H]`, result is `[B, H]`.
/// `out[b,k] = Σ_h a[b,k,h] · v[h]` — one shared vector against every batch.
///
/// `a` is `[B, K, H]`, `v` is `[H]`, result is `[B, K]`. Folds the batch into
/// the row axis so this is a single gemm rather than `B` of them.