18#ifndef NUMERICAL_GEMM_HPP
19#define NUMERICAL_GEMM_HPP
35#if defined(__GNUC__) && defined(__aarch64__)
37# define NUMERICAL_NEON_COMPILED
49#if defined(_MSC_VER) && !defined(__clang__)
50# define NUMERICAL_OUT_OF_LINE __declspec(noinline)
52# define NUMERICAL_OUT_OF_LINE [[gnu::noinline]]
59# define NUMERICAL_SCALAR_DEPTH _Pragma("clang loop vectorize(disable) interleave(disable)")
61# define NUMERICAL_SCALAR_DEPTH
67# define NUMERICAL_UNROLL_BLOCK _Pragma("GCC unroll 32")
69# define NUMERICAL_UNROLL_BLOCK
92#ifdef NUMERICAL_NEON_COMPILED
102#ifdef NUMERICAL_X86_COMPILED
115 for (
auto p {
static_cast<int>(
most)}; p > 0; --p) {
135 static std::atomic<std::uint8_t>
found {0xFF};
147 const unsigned cap {
static_cast<unsigned>(
gemm_path_cap().load(std::memory_order_relaxed))};
153 gemm_path_found().store(
static_cast<std::uint8_t
>((
cap << 3U) |
static_cast<unsigned>(
path)), std::memory_order_relaxed);
168 const double factor {
f};
169#ifdef NUMERICAL_X86_COMPILED
201#ifdef NUMERICAL_X86_COMPILED
226#ifdef NUMERICAL_X86_COMPILED
247 const std::complex<double>*
f,
248 const std::complex<double>* x,
253#ifdef NUMERICAL_X86_COMPILED
257 auto*
const into {
reinterpret_cast<double*
>(
y)};
258 const auto*
const scales {
reinterpret_cast<const double*
>(
f)};
259 const auto*
const rows {
reinterpret_cast<const double*
>(x)};
275 const std::complex<double>&
f,
276 const std::complex<double>* x,
279 const std::complex<double> factor {
f};
280#ifdef NUMERICAL_X86_COMPILED
292 const std::complex<double>* x,
293 const std::complex<double>&
f,
296 const std::complex<double> factor {
f};
297#ifdef NUMERICAL_X86_COMPILED
313 const double factor {
f};
314#ifdef NUMERICAL_X86_COMPILED
331 const std::complex<float>&
f,
332 const std::complex<float>* x,
335 const std::complex<float> factor {
f};
336#ifdef NUMERICAL_X86_COMPILED
350 const std::complex<double>&
f,
351 const std::complex<double>* x,
354 const std::complex<double> factor {
f};
355#ifdef NUMERICAL_X86_COMPILED
373#ifdef NUMERICAL_X86_COMPILED
388 template <split_complex C>
393#ifdef NUMERICAL_X86_COMPILED
403 const std::complex<double>*
y,
411 const std::complex<float>*
y,
418 template <split_complex C>
426 for (std::size_t r {0}; r < rows; ++r) {
432 inline void row_dots(
const std::complex<double>* m,
435 const std::complex<double>* x,
437 std::complex<double>*
out)
446 const std::complex<float>* x,
448 std::complex<float>*
out)
462#ifdef NUMERICAL_X86_COMPILED
464 for (std::size_t r {0}; r < rows; ++r) {
478 template <
typename T>
479 inline constexpr std::size_t
gemm_nr {
sizeof(
T) >= 16 ? std::size_t {6} : std::size_t {96} /
sizeof(
T)};
500 template <
typename T>
510#ifdef NUMERICAL_X86_COMPILED
521 template <
typename T>
532 template <
typename T>
540 for (std::size_t p {0}; p <
kc; ++p) {
541 for (std::size_t i {0}; i <
gemm_mr; ++i) {
543 for (std::size_t j {0}; j < nr; ++j) {
544 tile[(i * nr) + j] +=
ai *
bp[(p *
ldb) + j];
555 template <
typename T>
556 requires std::is_same_v<T, float> || std::is_same_v<T, double>
563 using lane =
typename native_vector<T>::type;
569 std::array<lane, gemm_mr * lanes>
acc {};
570# pragma GCC unroll 32
571 for (std::size_t q {0}; q <
acc.size(); ++q) {
578 for (std::size_t p {0}; p <
kc; ++p) {
579 std::array<lane, lanes> b {};
580 std::memcpy(b.data(),
bp + (p *
ldb),
sizeof(b));
582 for (std::size_t i {0}; i <
gemm_mr; ++i) {
585 for (std::size_t j {0}; j <
lanes; ++j) {
590# pragma GCC unroll 32
591 for (std::size_t q {0}; q <
acc.size(); ++q) {
599#ifdef NUMERICAL_NEON_COMPILED
613 static_assert(
gemm_mr == 4 &&
gemm_nr<double> == 12,
"two rows of A a vector, twelve columns six vectors");
614 constexpr std::size_t
lanes {6};
615 std::array<float64x2_t, 4 * lanes>
acc {};
617 for (std::size_t q {0}; q <
acc.size(); ++q) {
620 for (std::size_t p {0}; p <
kc; ++p) {
624 for (std::size_t j {0}; j <
lanes; ++j) {
633 for (std::size_t q {0}; q <
acc.size(); ++q) {
646 static_assert(
gemm_mr == 4 &&
gemm_nr<float> == 24,
"four rows of A a vector, 24 columns six vectors");
647 constexpr std::size_t
lanes {6};
648 std::array<float32x4_t, 4 * lanes>
acc {};
650 for (std::size_t q {0}; q <
acc.size(); ++q) {
653 for (std::size_t p {0}; p <
kc; ++p) {
656 for (std::size_t j {0}; j <
lanes; ++j) {
665 for (std::size_t q {0}; q <
acc.size(); ++q) {
676 template <std::size_t
Mr,
684 for (std::size_t
ir {0};
ir < rows;
ir +=
Mr) {
685 const std::size_t mr {std::min(
Mr, rows -
ir)};
688 for (std::size_t p {0}; p <
kc; ++p) {
689 for (std::size_t i {0}; i <
Mr; ++i) {
690 sliver[(p *
Mr) + i] = a[((
ir + i) * lda) + p];
695 for (std::size_t p {0}; p <
kc; ++p) {
696 for (std::size_t i {0}; i <
Mr; ++i) {
697 sliver[(p *
Mr) + i] = i < mr ? a[((
ir + i) * lda) + p] :
T {};
707 template <std::size_t
Nr,
715 for (std::size_t
jr {0};
jr < cols;
jr +=
Nr) {
716 const std::size_t width {std::min(
Nr, cols -
jr)};
718 for (std::size_t p {0}; p <
kc; ++p) {
719 for (std::size_t j {0}; j <
Nr; ++j) {
730 template <std::size_t
Nr,
738 for (std::size_t
jr {0};
jr < cols;
jr +=
Nr) {
739 const std::size_t width {std::min(
Nr, cols -
jr)};
741 for (std::size_t j {0}; j <
Nr; ++j) {
743 for (std::size_t p {0}; p <
kc; ++p) {
751 template <
typename T>
754 std::size_t&
ld_out)
noexcept
762 template <
typename T>
771 for (std::size_t j {0}; j < cols; ++j) {
772 for (std::size_t i {0}; i < rows; ++i) {
773 scratch[(i * cols) + j] = x[(j * ld) + i];
784 template <std::size_t
Nr,
798 template <
typename Kernel>
801 return Kernel::reads_b_in_place(
ldb);
805 template <
typename T>
817 template <
typename T>
821 std::size_t j)
noexcept
823 return b[(p *
ldb) + j];
832 template <std::size_t
Nr,
846 template <
typename Kernel>
854 template <
typename T>
866 template <
typename T>
870 std::size_t j)
noexcept
872 return bt[(j *
ldbt) + p];
881 template <std::size_t
Mr,
889 for (std::size_t
ir {0};
ir < rows;
ir +=
Mr) {
890 const std::size_t mr {std::min(
Mr, rows -
ir)};
892 for (std::size_t p {0}; p <
kc; ++p) {
894 for (std::size_t i {0}; i <
Mr; ++i) {
905 template <
typename Kernel,
915 Kernel::pack_a(a + (
i0 * lda) +
p0, lda, rows,
kc,
out);
919 template <
typename T>
931 template <
typename T>
935 std::size_t p)
noexcept
937 return a[(i * lda) + p];
946 template <
typename Kernel,
960 template <
typename T>
972 template <
typename T>
976 std::size_t p)
noexcept
986 template <
typename T>
1034 for (std::size_t i {0}; i < rows; ++i) {
1035 std::copy_n(c + (i * ldc), cols,
accumulators.data() + (i *
nr));
1038 for (std::size_t i {0}; i < rows; ++i) {
1039 std::copy_n(
accumulators.data() + (i *
nr), cols, c + (i * ldc));
1044#ifdef NUMERICAL_NEON_COMPILED
1046 template <
typename T>
1059 std::size_t cols)
noexcept
1063 for (std::size_t i {0}; i < rows; ++i) {
1064 std::copy_n(c + (i * ldc), cols,
accumulators.data() + (i * nr));
1067 for (std::size_t i {0}; i < rows; ++i) {
1068 std::copy_n(
accumulators.data() + (i * nr), cols, c + (i * ldc));
1074#ifdef NUMERICAL_AMX_COMPILED
1079 static constexpr std::size_t mr {
amx_mr};
1080 static constexpr std::size_t nr {
amx_nr};
1083 static constexpr std::size_t depth {512};
1084 static constexpr std::size_t block_rows {
gemm_mc};
1087 static constexpr std::size_t b_in_place_rows {block_rows};
1093 static constexpr std::size_t serial_below {std::size_t {1} << 24};
1097 static constexpr bool reads_b_in_place(std::size_t
ldb)
noexcept
1103 static void pack_a(
const double* a,
1107 double*
out)
noexcept
1113 static void tile(std::size_t
kc,
1120 std::size_t cols)
noexcept
1131 static constexpr std::size_t depth {512};
1132 static constexpr std::size_t block_rows {
gemm_mc};
1133 static constexpr std::size_t b_in_place_rows {block_rows};
1135 static constexpr std::size_t serial_below {std::size_t {1} << 24};
1138 static constexpr bool reads_b_in_place(std::size_t
ldb)
noexcept
1143 static void pack_a(
const float* a,
1147 float*
out)
noexcept
1152 static void tile(std::size_t
kc,
1159 std::size_t cols)
noexcept
1171#ifdef NUMERICAL_X86_COMPILED
1185 template <
typename T>
1190#if defined(__GNUC__) && !defined(__clang__)
1194 return sum + (a * b);
1200 template <
typename R>
1203 std::complex<R> b)
noexcept
1224 template <term_rounding Mode>
1228 template <
typename T>
1238 template <
typename T>
1244 return std::fma(a, b,
sum);
1247#ifdef NUMERICAL_X86_COMPILED
1250 template <std::
size_t R, std::
size_t N,
typename T>
1259 std::array<T, R * N>
sums {};
1261 for (std::size_t e {0}; e <
R *
N; ++e) {
1262 sums[e] = c[((e /
N) * ldc) + (e %
N)];
1264 for (std::size_t p {0}; p < k; ++p) {
1266 for (std::size_t r {0}; r <
R; ++r) {
1267 const T ap {a[(r * lda) + p]};
1269 for (std::size_t j {0}; j <
N; ++j) {
1270 sums[(r *
N) + j] = std::fma(
ap, b[(p *
ldb) + j],
sums[(r *
N) + j]);
1275 for (std::size_t e {0}; e <
R *
N; ++e) {
1276 c[((e /
N) * ldc) + (e %
N)] =
sums[e];
1281 template <
typename T>
1289 for (std::size_t p {0}; p < k; ++p) {
1291 for (std::size_t j {0}; j < n; ++j) {
1292 c[j] = std::fma(
ap, b[(p *
ldb) + j], c[j]);
1303 template <std::
size_t R, std::
size_t N, term_rounding Mode,
typename T>
1310 std::size_t ldc)
noexcept
1312#ifdef NUMERICAL_X86_COMPILED
1319 std::array<T, R * N>
sums {};
1321 for (std::size_t e {0}; e <
R *
N; ++e) {
1322 sums[e] = c[((e /
N) * ldc) + (e %
N)];
1325 for (std::size_t p {0}; p < k; ++p) {
1327 for (std::size_t r {0}; r <
R; ++r) {
1328 const T ap {a[(r * lda) + p]};
1330 for (std::size_t j {0}; j <
N; ++j) {
1336 for (std::size_t e {0}; e <
R *
N; ++e) {
1337 c[((e /
N) * ldc) + (e %
N)] =
sums[e];
1343 template <std::
size_t R, term_rounding Mode,
typename T>
1351 std::size_t ldc)
noexcept
1353 static_assert(
narrow_columns == 8,
"the switch below names each narrower last block");
1380 template <term_rounding Mode,
typename T>
1398 for (std::size_t i {first}; i < m; ++i) {
1405#ifdef NUMERICAL_X86_COMPILED
1412 for (std::size_t p {0}; p < k; ++p) {
1413 const T aip {a[(i * lda) + p]};
1414 for (std::size_t j {0}; j < n; ++j) {
1432#ifdef NUMERICAL_NEON_COMPILED
1444 template <
typename T>
1455#ifdef NUMERICAL_X86_COMPILED
1456 if constexpr (std::is_same_v<T, double> || std::is_same_v<T, float>) {
1464 if constexpr (std::is_floating_point_v<T>) {
1478 template <
typename T>
1481 const std::size_t
miss {
static_cast<std::size_t
>(std::bit_cast<std::uintptr_t>(at) %
packed_line)};
1495 template <
typename T>
1501 static constexpr std::size_t
kept_limit {(std::size_t {32} << 20U) /
sizeof(
T)};
1507 a_ {aligned(storage_.data())},
1508 b_ {aligned(storage_.data() +
a_count + padding)}
1518 kept() = storage_.size() <= kept_limit ? std::move(storage_) :
buffer<T> {};
1565 template <
typename Kernel>
1568 std::size_t k)
noexcept
1570 return m * n * k >= Kernel::serial_below;
1576 template <
typename Kernel>
1578 Kernel::prefetch_rows(c, ldc);
1582 template <
typename Kernel,
1585 std::size_t )
noexcept
1590 template <
typename Kernel,
1592 requires prefetches_next_row_of_c<Kernel>
1594 std::size_t ldc)
noexcept
1596 Kernel::prefetch_rows(c, ldc);
1600 template <
typename Kernel>
1603 return Kernel::block_rows;
1607 template <
typename Kernel>
1608 requires requires(std::size_t kc)
1610 Kernel::task_rows_for(kc);
1615 return Kernel::task_rows_for(kc);
1627 template <
typename Kernel,
1629 typename B = b_rows,
1630 typename A = a_rows>
1640 const T* a_packed =
nullptr)
1642 constexpr std::size_t mr {Kernel::mr};
1643 constexpr std::size_t nr {Kernel::nr};
1644 constexpr std::size_t nc_max {nr * 128};
1645 const std::size_t
a_rows {((m + mr) - 1) / mr * mr};
1646 const std::size_t b_cols {((std::min(nc_max, n) + nr) - 1) / nr * nr};
1647 const packing_storage<T> packed {a_packed !=
nullptr ? 0 :
a_rows * std::min(Kernel::depth, k), b_cols * std::min(Kernel::depth, k)};
1648 const std::size_t a_blocks {a_packed !=
nullptr ? 0 : ((m + Kernel::block_rows) - 1) / Kernel::block_rows};
1649 const T*
const a_tiles {a_packed !=
nullptr ? a_packed : packed.a()};
1650 const std::size_t shared {gemm_shares<Kernel>(m, n, k) ? 1U : 0U};
1653 const bool in_place {B::template in_place<Kernel>(ldb) || (m <= Kernel::b_in_place_rows && B::template in_place<Kernel>(1))};
1654 for (std::size_t jc {0}; jc < n; jc += nc_max) {
1655 const std::size_t nc {std::min(nc_max, n - jc)};
1656 const std::size_t slivers {((nc + nr) - 1) / nr};
1657 for (std::size_t pc {0}; pc < k; pc += Kernel::depth) {
1658 const std::size_t kc {std::min(Kernel::depth, k - pc)};
1664 if (piece < groups) {
1667 const std::size_t whole {in_place ? width - (width % nr) : 0};
1668 if (whole < width) {
1669 B::template pack<nr>(b, ldb, pc, jc + jr + whole, kc, width - whole, packed.b() + ((jr + whole) * kc));
1673 const std::size_t ic {(piece - groups) * Kernel::block_rows};
1674 A::template pack<Kernel>(a, lda, ic, pc, std::min(Kernel::block_rows, m - ic), kc, packed.a() + (ic * kc));
1679 const std::size_t tiles {((m + mr) - 1) / mr};
1681 const std::size_t task_rows {std::clamp(((tiles + row_tasks) - 1) / row_tasks, std::size_t {1}, task_rows_cap<Kernel>(kc) / mr) * mr};
1682 const std::size_t task_count {((m + task_rows) - 1) / task_rows};
1684 const std::size_t ic {(task / groups) * task_rows};
1687 const std::size_t ic_end {std::min(ic + task_rows, m)};
1688 [[maybe_unused]]
const typename Kernel::session on {};
1692 for (std::size_t ir {ic}; ir < ic_end; ir += mr) {
1693 for (std::size_t s {first}; s < last; ++s) {
1694 const std::size_t jr {s * nr};
1695 const bool direct {in_place && jr + nr <= nc};
1696 if (ir + mr < ic_end) {
1697 prefetch_next_row<Kernel>(c + ((ir + mr) * ldc) + jc + jr, ldc);
1699 Kernel::tile(kc, a_tiles + (ir * kc), direct ? b + (pc * ldb) + jc + jr : packed.b() + (jr * kc),
1700 direct ? ldb : nr, c + (ir * ldc) + jc + jr, ldc, std::min(mr, m - ir), std::min(nr, nc - jr));
1716 template <
typename Kernel,
1718 typename B = b_rows,
1719 typename A = a_rows>
1730 constexpr std::size_t mr {Kernel::mr};
1731 constexpr std::size_t nr {Kernel::nr};
1732 constexpr std::size_t nc_max {nr * 128};
1733 const std::size_t
a_rows {((n + mr) - 1) / mr * mr};
1734 const std::size_t b_cols {((std::min(nc_max, n) + nr) - 1) / nr * nr};
1736 const std::size_t a_blocks {((n + Kernel::block_rows) - 1) / Kernel::block_rows};
1737 const std::size_t shared {gemm_shares<Kernel>(n, n / 2, k) ? 1U : 0U};
1738 const bool in_place {B::template in_place<Kernel>(ldb)};
1740 const auto meets = [lower](std::size_t r0, std::size_t r1, std::size_t j0, std::size_t j1) {
return lower ? j0 < r1 : r0 < j1; };
1741 const auto inside = [lower](std::size_t r0, std::size_t r1, std::size_t j0, std::size_t j1) {
return lower ? j1 <= r0 + 1 : r1 <= j0 + 1; };
1742 for (std::size_t jc {0}; jc < n; jc += nc_max) {
1743 const std::size_t nc {std::min(nc_max, n - jc)};
1744 const std::size_t slivers {((nc + nr) - 1) / nr};
1745 for (std::size_t pc {0}; pc < k; pc += Kernel::depth) {
1746 const std::size_t kc {std::min(Kernel::depth, k - pc)};
1749 if (piece < groups) {
1752 const std::size_t whole {in_place ? width - (width % nr) : 0};
1753 if (whole < width) {
1754 B::template pack<nr>(b, ldb, pc, jc + jr + whole, kc, width - whole, packed.b() + ((jr + whole) * kc));
1758 const std::size_t ic {(piece - groups) * Kernel::block_rows};
1759 const std::size_t rows {std::min(Kernel::block_rows, n - ic)};
1760 if (meets(ic, ic + rows, jc, jc + nc)) {
1761 A::template pack<Kernel>(a, lda, ic, pc, rows, kc, packed.a() + (ic * kc));
1764 const std::size_t tiles {((n + mr) - 1) / mr};
1766 const std::size_t task_rows {std::clamp(((tiles + row_tasks) - 1) / row_tasks, std::size_t {1}, task_rows_cap<Kernel>(kc) / mr) * mr};
1767 const std::size_t task_count {((n + task_rows) - 1) / task_rows};
1769 const std::size_t ic {(task / groups) * task_rows};
1772 const std::size_t ic_end {std::min(ic + task_rows, n)};
1773 [[maybe_unused]]
const typename Kernel::session on {};
1774 for (std::size_t ir {ic}; ir < ic_end; ir += mr) {
1775 const std::size_t rows {std::min(mr, n - ir)};
1776 for (std::size_t s {first}; s < last; ++s) {
1777 const std::size_t jr {s * nr};
1778 const std::size_t j0 {jc + jr};
1779 const std::size_t cols {std::min(nr, nc - jr)};
1780 if (!meets(ir, ir + rows, j0, j0 + cols)) {
1783 const bool direct {in_place && jr + nr <= nc};
1784 const T*
const bp {direct ? b + (pc * ldb) + j0 : packed.b() + (jr * kc)};
1785 const std::size_t ldbp {direct ? ldb : nr};
1786 T*
const block {c + (ir * ldc) + j0};
1787 if (inside(ir, ir + rows, j0, j0 + cols)) {
1788 Kernel::tile(kc, packed.a() + (ir * kc), bp, ldbp, block, ldc, rows, cols);
1791 std::array<T, mr * nr> copy {};
1792 for (std::size_t i {0}; i < rows; ++i) {
1793 std::copy_n(block + (i * ldc), cols, copy.data() + (i * nr));
1795 Kernel::tile(kc, packed.a() + (ir * kc), bp, ldbp, copy.data(), nr, rows, cols);
1796 for (std::size_t i {0}; i < rows; ++i) {
1797 for (std::size_t j {0}; j < cols; ++j) {
1798 if (lower ? j0 + j <= ir + i : j0 + j >= ir + i) {
1799 block[(i * ldc) + j] = copy[(i * nr) + j];
1810#ifdef NUMERICAL_X86_COMPILED
1812 struct avx512_tile_kernel
1814 static constexpr std::size_t mr {avx512_mr};
1815 static constexpr std::size_t nr {avx512_nr};
1816 static constexpr std::size_t depth {
gemm_kc};
1817 static constexpr std::size_t block_rows {96};
1818 static constexpr std::size_t b_in_place_rows {mr};
1822 static constexpr std::size_t serial_below {1};
1830 static constexpr bool reads_b_in_place(std::size_t ldb)
noexcept
1836 static void pack_a(
const double* a,
1842 gemm_pack_a<mr>(a, lda, rows, kc, out);
1846 static void tile(std::size_t kc,
1853 std::size_t cols)
noexcept
1855 avx512_tile_f64(kc, ap, bp, ldb, c, ldc, rows, cols);
1860 struct avx2_tile_kernel
1862 static constexpr std::size_t mr {avx2_mr};
1863 static constexpr std::size_t nr {avx2_nr};
1864 static constexpr std::size_t depth {
gemm_kc};
1865 static constexpr std::size_t block_rows {72};
1866 static constexpr std::size_t b_in_place_rows {mr};
1873 [[nodiscard]]
static constexpr std::size_t task_rows_for(std::size_t kc)
noexcept
1875 return kc <= 128 ? 36 : block_rows;
1883 static void prefetch_rows(
const double* c,
1884 std::size_t ldc)
noexcept
1886 for (std::size_t r {0}; r < mr; ++r) {
1887 _mm_prefetch(
reinterpret_cast<const char*
>(c + (r * ldc)), _MM_HINT_T0);
1894 static constexpr std::size_t serial_below {1};
1902 static constexpr bool reads_b_in_place(std::size_t ldb)
noexcept
1908 static void pack_a(
const double* a,
1914 gemm_pack_a<mr>(a, lda, rows, kc, out);
1918 static void tile(std::size_t kc,
1925 std::size_t cols)
noexcept
1927 avx2_tile_f64(kc, ap, bp, ldb, c, ldc, rows, cols);
1931 struct avx512_tile_kernel_f32
1933 static constexpr std::size_t mr {avx512_mr};
1934 static constexpr std::size_t nr {avx512_nr_f32};
1935 static constexpr std::size_t depth {
gemm_kc};
1936 static constexpr std::size_t block_rows {96};
1937 static constexpr std::size_t b_in_place_rows {mr};
1939 static constexpr std::size_t serial_below {1};
1944 static constexpr bool reads_b_in_place(std::size_t ldb)
noexcept
1949 static void pack_a(
const float* a,
1955 gemm_pack_a<mr>(a, lda, rows, kc, out);
1958 static void tile(std::size_t kc,
1965 std::size_t cols)
noexcept
1967 avx512_tile_f32(kc, ap, bp, ldb, c, ldc, rows, cols);
1972 struct avx2_tile_kernel_f32
1974 static constexpr std::size_t mr {avx2_mr};
1975 static constexpr std::size_t nr {avx2_nr_f32};
1976 static constexpr std::size_t depth {
gemm_kc};
1977 static constexpr std::size_t block_rows {72};
1978 static constexpr std::size_t b_in_place_rows {mr};
1980 static constexpr std::size_t serial_below {1};
1982 [[nodiscard]]
static constexpr std::size_t task_rows_for(std::size_t kc)
noexcept
1984 return kc <= 128 ? 36 : block_rows;
1990 static constexpr bool reads_b_in_place(std::size_t ldb)
noexcept
1995 static void pack_a(
const float* a,
2001 gemm_pack_a<mr>(a, lda, rows, kc, out);
2004 static void tile(std::size_t kc,
2011 std::size_t cols)
noexcept
2013 avx2_tile_f32(kc, ap, bp, ldb, c, ldc, rows, cols);
2018 struct avx512_complex_tile_kernel
2020 static constexpr std::size_t mr {avx512_complex_mr};
2021 static constexpr std::size_t nr {avx512_complex_nr};
2022 static constexpr std::size_t depth {
gemm_kc / 2};
2023 static constexpr std::size_t block_rows {96};
2024 static constexpr std::size_t b_in_place_rows {mr};
2026 static constexpr std::size_t serial_below {1};
2034 static constexpr bool reads_b_in_place(std::size_t ldb)
noexcept
2040 static void pack_a(
const std::complex<double>* a,
2044 std::complex<double>* out)
2046 gemm_pack_a<mr>(a, lda, rows, kc, out);
2050 static void tile(std::size_t kc,
2051 const std::complex<double>* ap,
2052 const std::complex<double>* bp,
2054 std::complex<double>* c,
2057 std::size_t cols)
noexcept
2059 avx512_tile_c64(kc, ap, bp, ldb, c, ldc, rows, cols);
2064 struct avx2_complex_tile_kernel
2066 static constexpr std::size_t mr {avx2_complex_mr};
2067 static constexpr std::size_t nr {avx2_complex_nr};
2068 static constexpr std::size_t depth {
gemm_kc / 2};
2069 static constexpr std::size_t block_rows {72};
2070 static constexpr std::size_t b_in_place_rows {mr};
2072 static constexpr std::size_t serial_below {1};
2080 static constexpr bool reads_b_in_place(std::size_t ldb)
noexcept
2086 static void pack_a(
const std::complex<double>* a,
2090 std::complex<double>* out)
2092 gemm_pack_a<mr>(a, lda, rows, kc, out);
2096 static void tile(std::size_t kc,
2097 const std::complex<double>* ap,
2098 const std::complex<double>* bp,
2100 std::complex<double>* c,
2103 std::size_t cols)
noexcept
2105 avx2_tile_c64(kc, ap, bp, ldb, c, ldc, rows, cols);
2109 struct avx512_complex_tile_kernel_f32
2111 static constexpr std::size_t mr {avx512_complex_mr};
2112 static constexpr std::size_t nr {avx512_complex_nr_f32};
2113 static constexpr std::size_t depth {
gemm_kc};
2114 static constexpr std::size_t block_rows {96};
2115 static constexpr std::size_t b_in_place_rows {mr};
2117 static constexpr std::size_t serial_below {1};
2122 static constexpr bool reads_b_in_place(std::size_t ldb)
noexcept
2127 static void pack_a(
const std::complex<float>* a,
2131 std::complex<float>* out)
2133 gemm_pack_a<mr>(a, lda, rows, kc, out);
2136 static void tile(std::size_t kc,
2137 const std::complex<float>* ap,
2138 const std::complex<float>* bp,
2140 std::complex<float>* c,
2143 std::size_t cols)
noexcept
2145 avx512_tile_c32(kc, ap, bp, ldb, c, ldc, rows, cols);
2150 struct avx2_complex_tile_kernel_f32
2152 static constexpr std::size_t mr {avx2_complex_mr};
2153 static constexpr std::size_t nr {avx2_complex_nr_f32};
2154 static constexpr std::size_t depth {
gemm_kc};
2155 static constexpr std::size_t block_rows {72};
2156 static constexpr std::size_t b_in_place_rows {mr};
2158 static constexpr std::size_t serial_below {1};
2163 static constexpr bool reads_b_in_place(std::size_t ldb)
noexcept
2168 static void pack_a(
const std::complex<float>* a,
2172 std::complex<float>* out)
2174 gemm_pack_a<mr>(a, lda, rows, kc, out);
2177 static void tile(std::size_t kc,
2178 const std::complex<float>* ap,
2179 const std::complex<float>* bp,
2181 std::complex<float>* c,
2184 std::size_t cols)
noexcept
2186 avx2_tile_c32(kc, ap, bp, ldb, c, ldc, rows, cols);
2193 template <
typename T>
2204 (void)m, (
void)n, (void)k, (
void)a, (void)lda, (
void)b, (void)ldb, (
void)c, (void)ldc;
2211 template <
typename B,
2212 typename A = a_rows>
2224#ifdef NUMERICAL_AMX_COMPILED
2225 if (path == gemm_path::amx && amx_suits(m, n, k)) {
2226 gemm_packed<amx_tile_kernel, double, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2230#ifdef NUMERICAL_NEON_COMPILED
2231 if (path == gemm_path::neon || path == gemm_path::amx) {
2232 gemm_packed<neon_tile_kernel<double>, double, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2236#ifdef NUMERICAL_X86_COMPILED
2237 if (path == gemm_path::avx512) {
2238 gemm_packed<avx512_tile_kernel, double, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2241 if (path == gemm_path::avx2) {
2242 gemm_packed<avx2_tile_kernel, double, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2247 (void)m, (
void)n, (void)k, (
void)a, (void)lda, (
void)b, (void)ldb, (
void)c, (void)ldc;
2255 template <
typename A,
2268#ifdef NUMERICAL_AMX_COMPILED
2269 if (path == gemm_path::amx && amx_suits(n, n, k)) {
2270 gemm_packed_triangle<amx_tile_kernel, double, B, A>(n, k, a, lda, b, ldb, c, ldc, lower);
2274#ifdef NUMERICAL_NEON_COMPILED
2275 if (path == gemm_path::neon || path == gemm_path::amx) {
2276 gemm_packed_triangle<neon_tile_kernel<double>, double, B, A>(n, k, a, lda, b, ldb, c, ldc, lower);
2280#ifdef NUMERICAL_X86_COMPILED
2281 if (path == gemm_path::avx512) {
2282 gemm_packed_triangle<avx512_tile_kernel, double, B, A>(n, k, a, lda, b, ldb, c, ldc, lower);
2285 if (path == gemm_path::avx2) {
2286 gemm_packed_triangle<avx2_tile_kernel, double, B, A>(n, k, a, lda, b, ldb, c, ldc, lower);
2291 gemm_packed_triangle<vector_tile_kernel<double>, double, B, A>(n, k, a, lda, b, ldb, c, ldc, lower);
2305 return gemm_double_elsewhere<b_rows>(m, n, k, a, lda, b, ldb, c, ldc);
2311 template <
typename B,
2312 typename A = a_rows>
2323#ifdef NUMERICAL_NEON_COMPILED
2325# ifdef NUMERICAL_AMX_COMPILED
2326 if (path == gemm_path::amx && amx_suits_f32(m, n, k)) {
2327 gemm_packed<amx_tile_kernel_f32, float, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2331 if (path == gemm_path::neon || path == gemm_path::amx) {
2332 gemm_packed<neon_tile_kernel<float>, float, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2336#ifdef NUMERICAL_X86_COMPILED
2338 if (x86_path == gemm_path::avx512) {
2339 gemm_packed<avx512_tile_kernel_f32, float, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2342 if (x86_path == gemm_path::avx2) {
2343 gemm_packed<avx2_tile_kernel_f32, float, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2347 (void)m, (
void)n, (void)k, (
void)a, (void)lda, (
void)b, (void)ldb, (
void)c, (void)ldc;
2362 return gemm_float_elsewhere<b_rows>(m, n, k, a, lda, b, ldb, c, ldc);
2365 template <
typename T>
2366 void gemm(std::size_t m,
2403#ifdef NUMERICAL_AMX_COMPILED
2404 if (path == gemm_path::amx) {
2414 template <
typename R>
2426 template <
typename R>
2440 template <split_complex Z,
2441 typename B = b_rows,
2442 typename A = a_rows>
2452 typename Z::value_type* ar,
2453 typename Z::value_type* br,
2454 typename Z::value_type* cr)
2456 using R =
typename Z::value_type;
2458 R*
const row {ar + (i * 3 * kb)};
2459 for (std::size_t p {0}; p < kb; ++p) {
2460 const Z value {A::at(a, lda, i, pc + p)};
2461 row[p] = value.real();
2462 row[kb + p] = -value.imag();
2463 row[(2 * kb) + p] = value.imag();
2467 R*
const row {br + (p * 2 * nb)};
2468 for (std::size_t j {0}; j < nb; ++j) {
2469 const Z value {B::at(b, ldb, pc + p, jc + j)};
2470 row[j] = value.real();
2471 row[nb + j] = value.imag();
2474 gemm(m, nb, kb, ar, 3 * kb, br, 2 * nb, cr, 2 * nb);
2475 gemm(m, nb, kb, ar + kb, 3 * kb, br + nb, 2 * nb, cr, 2 * nb);
2476 gemm(m, nb, kb, ar, 3 * kb, br + nb, 2 * nb, cr + nb, 2 * nb);
2477 gemm(m, nb, kb, ar + (2 * kb), 3 * kb, br, 2 * nb, cr + nb, 2 * nb);
2485 template <split_complex Z,
2486 typename B = b_rows,
2487 typename A = a_rows>
2497 typename Z::value_type* ar,
2498 typename Z::value_type* br,
2499 typename Z::value_type* cr)
2501 using R =
typename Z::value_type;
2503 for (std::size_t p {0}; p < kb; ++p) {
2504 const Z value {A::at(a, lda, i, pc + p)};
2505 ar[(i * 2 * kb) + p] = value.real();
2506 ar[(i * 2 * kb) + kb + p] = -value.imag();
2510 R*
const top {br + (p * 2 * nb)};
2511 R*
const bottom {br + ((kb + p) * 2 * nb)};
2512 for (std::size_t j {0}; j < nb; ++j) {
2513 const Z value {B::at(b, ldb, pc + p, jc + j)};
2514 top[j] = value.real();
2515 top[nb + j] = value.imag();
2516 bottom[j] = value.imag();
2517 bottom[nb + j] = -value.real();
2520 gemm(m, 2 * nb, 2 * kb, ar, 2 * kb, br, 2 * nb, cr, 2 * nb);
2531 template <split_complex Z,
2532 typename B = b_rows,
2533 typename A = a_rows>
2544 using R =
typename Z::value_type;
2547 const std::size_t deepest {std::min(k,
split_depth)};
2549 kept.
a.resize(std::max(kept.a.size(), m * (parts ? 3 : 2) * deepest));
2550 kept.b.resize(std::max(kept.b.size(), (parts ? 2 : 4) * deepest * widest));
2551 kept.c.resize(std::max(kept.c.size(), m * 2 * widest));
2552 R*
const ar {kept.a.data()};
2553 R*
const br {kept.b.data()};
2554 R*
const cr {kept.c.data()};
2558 for (std::size_t j {0}; j < nb; ++j) {
2559 cr[(i * 2 * nb) + j] = c[(i * ldc) + jc + j].real();
2560 cr[(i * 2 * nb) + nb + j] = c[(i * ldc) + jc + j].imag();
2563 for (std::size_t pc {0}; pc < k; pc +=
split_depth) {
2564 const std::size_t kb {std::min(
split_depth, k - pc)};
2566 split_parts_block<Z, B, A>(m, nb, kb, pc, jc, a, lda, b, ldb, ar, br, cr);
2569 split_expanded_block<Z, B, A>(m, nb, kb, pc, jc, a, lda, b, ldb, ar, br, cr);
2573 for (std::size_t j {0}; j < nb; ++j) {
2574 c[(i * ldc) + jc + j] = Z {cr[(i * 2 * nb) + j], cr[(i * 2 * nb) + nb + j]};
2578 for (
buffer<R>* operand : {&kept.a, &kept.b, &kept.c}) {
2602 std::size_t k)
noexcept
2605#ifdef NUMERICAL_NEON_COMPILED
2606 if (path == gemm_path::neon) {
2607 return k <= 16 || (m <= 64 && k <= 64);
2610#ifdef NUMERICAL_X86_COMPILED
2611 if (path == gemm_path::avx2 || path == gemm_path::avx512) {
2615 (void)m, (
void)n, (void)k;
2620 template <
typename T>
2623 std::size_t )
noexcept
2630 [[nodiscard]]
inline bool plain_loop_suits<double>(std::size_t m,
2632 std::size_t k)
noexcept
2639 template <
typename T>
2650 if (plain_loop_suits<T>(m, n, k)) {
2651 gemm_rows(m, n, k, a, lda, b, ldb, c, ldc);
2655 gemm_packed<vector_tile_kernel<T>>(m, n, k, a, lda, b, ldb, c, ldc);
2659#ifdef NUMERICAL_X86_COMPILED
2661 template <split_complex Z,
2664 using x86_complex_kernel = std::conditional_t<std::is_same_v<Z, std::complex<double>>, Double, Float>;
2670 template <
typename B,
2684 if (path == gemm_path::portable) {
2685 gemm_packed<vector_tile_kernel<T>, T, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2688#ifdef NUMERICAL_X86_COMPILED
2689 if (path == gemm_path::avx512) {
2690 gemm_packed<x86_complex_kernel<T, avx512_complex_tile_kernel, avx512_complex_tile_kernel_f32>, T, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2693 if (path == gemm_path::avx2) {
2694 gemm_packed<x86_complex_kernel<T, avx2_complex_tile_kernel, avx2_complex_tile_kernel_f32>, T, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2698 gemm_split<T, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2702 template <split_complex T>
2703 void gemm_large(std::size_t m,
2713 gemm_large_complex<b_rows, a_rows>(m, n, k, a, lda, b, ldb, c, ldc);
2739 template <
typename T>
2740 void gemm(std::size_t m,
2752 if (m != 0 && n != 0 && k != 0) {
2759 gemm_rows(m, n, k, a, lda, b, ldb, c, ldc);
2766 template <
typename B,
2778 if (!gemm_double_elsewhere<B, A>(m, n, k, a, lda, b, ldb, c, ldc)) {
2779 gemm_packed<vector_tile_kernel<double>, double, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2784 template <
typename B,
2796 if (!gemm_float_elsewhere<B, A>(m, n, k, a, lda, b, ldb, c, ldc)) {
2797 gemm_packed<vector_tile_kernel<float>, float, B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2802 template <
typename B,
2815 gemm_large_complex<B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2826 template <
typename A,
2841 if (m != 0 && n != 0 && k != 0) {
2842 gemm_large_complex<B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2850 std::size_t a_ld {};
2851 std::size_t b_ld {};
2852 const T*
const left {A::rows_of(a, lda, m, k, a_rows_copy, a_ld)};
2853 const T*
const right {B::rows_of(b, ldb, k, n, b_rows_copy, b_ld)};
2854 gemm_rows(m, n, k, left, a_ld, right, b_ld, c, ldc);
2857 gemm_large_layouts<B, A>(m, n, k, a, lda, b, ldb, c, ldc);
2861 template <
typename T>
2872 gemm_layouts<a_rows, b_transposed>(m, n, k, a, lda, bt, ldbt, c, ldc);
2876 template <
typename T>
2887 gemm_layouts<a_transposed, b_rows>(m, n, k, at, ldat, b, ldb, c, ldc);
2891 template <
typename T>
2902 gemm_layouts<a_transposed, b_transposed>(m, n, k, at, ldat, bt, ldbt, c, ldc);
2918 template <
typename T>
2931 template <
typename T>
2935 std::size_t lda)
noexcept
2937 return packed_left<T> {.
kernel = left_kernel::plain, .m = m, .k = k, .a = a, .lda = lda, .packed =
nullptr, .granule = 1};
2944 template <
typename T>
2947 std::size_t rows)
noexcept
2951 part.a = left.a + (r0 * left.lda);
2952 part.packed = left.packed ==
nullptr ? nullptr : left.packed + (r0 * left.k);
2958 template <
typename T>
2961 std::size_t )
noexcept
2963 return left_kernel::plain;
2969 [[nodiscard]]
inline left_kernel left_kernel_for<double>(std::size_t m,
2971 std::size_t k)
noexcept
2974 return left_kernel::plain;
2977#ifdef NUMERICAL_AMX_COMPILED
2978 if (path == gemm_path::amx && amx_suits(m, n, k)) {
2979 return left_kernel::amx;
2982#ifdef NUMERICAL_NEON_COMPILED
2983 if (path == gemm_path::neon || path == gemm_path::amx) {
2984 return left_kernel::neon;
2987#ifdef NUMERICAL_X86_COMPILED
2988 if (path == gemm_path::avx512) {
2989 return left_kernel::avx512;
2991 if (path == gemm_path::avx2) {
2992 return left_kernel::avx2;
2996 return left_kernel::vector;
3000 template <
typename Kernel,
3008 const std::size_t
a_rows {((m + Kernel::mr) - 1) / Kernel::mr * Kernel::mr};
3009 storage.resize(std::max(storage.size(), (
a_rows * k) + (
packed_line /
sizeof(T)) + 1));
3011 const std::size_t blocks {((m + Kernel::block_rows) - 1) / Kernel::block_rows};
3013 const std::size_t ic {block * Kernel::block_rows};
3014 Kernel::pack_a(a + (ic * lda), lda, std::min(Kernel::block_rows, m - ic), k, out + (ic * k));
3025 template <
typename T>
3038 [[nodiscard]]
inline packed_left<double> pack_left<double>(std::size_t m,
3045 packed_left<double> left {.kernel = left_kernel_for<double>(m, n, k), .m = m, .k = k, .a = a, .lda = lda, .packed =
nullptr, .granule = 1};
3046#ifdef NUMERICAL_AMX_COMPILED
3047 if (left.kernel == left_kernel::amx) {
3048 left.packed = pack_left_for<amx_tile_kernel>(m, k, a, lda, storage);
3049 left.granule = amx_tile_kernel::mr;
3052#ifdef NUMERICAL_NEON_COMPILED
3053 if (left.kernel == left_kernel::neon) {
3054 left.packed = pack_left_for<neon_tile_kernel<double>>(m, k, a, lda, storage);
3055 left.granule = neon_tile_kernel<double>::mr;
3058#ifdef NUMERICAL_X86_COMPILED
3059 if (left.kernel == left_kernel::avx512) {
3060 left.packed = pack_left_for<avx512_tile_kernel>(m, k, a, lda, storage);
3061 left.granule = avx512_tile_kernel::mr;
3063 if (left.kernel == left_kernel::avx2) {
3064 left.packed = pack_left_for<avx2_tile_kernel>(m, k, a, lda, storage);
3065 left.granule = avx2_tile_kernel::mr;
3068 if (left.kernel == left_kernel::vector) {
3069 left.packed = pack_left_for<vector_tile_kernel<double>>(m, k, a, lda, storage);
3070 left.granule = vector_tile_kernel<double>::mr;
3079 template <
typename T>
3087 gemm(left.
m, n, left.
k, left.
a, left.
lda, b, ldb, c, ldc);
3103 if (left.
kernel == left_kernel::plain || left_kernel_for<double>(left.
m, n, left.
k) != left.
kernel) {
3104 gemm(left.
m, n, left.
k, left.
a, left.
lda, b, ldb, c, ldc);
3107#ifdef NUMERICAL_AMX_COMPILED
3108 if (left.
kernel == left_kernel::amx) {
3109 gemm_packed<amx_tile_kernel>(left.
m, n, left.
k, left.
a, left.
lda, b, ldb, c, ldc, left.
packed);
3113#ifdef NUMERICAL_NEON_COMPILED
3114 if (left.
kernel == left_kernel::neon) {
3115 gemm_packed<neon_tile_kernel<double>>(left.
m, n, left.
k, left.
a, left.
lda, b, ldb, c, ldc, left.
packed);
3119#ifdef NUMERICAL_X86_COMPILED
3120 if (left.
kernel == left_kernel::avx512) {
3121 gemm_packed<avx512_tile_kernel>(left.
m, n, left.
k, left.
a, left.
lda, b, ldb, c, ldc, left.
packed);
3124 if (left.
kernel == left_kernel::avx2) {
3125 gemm_packed<avx2_tile_kernel>(left.
m, n, left.
k, left.
a, left.
lda, b, ldb, c, ldc, left.
packed);
3129 gemm_packed<vector_tile_kernel<double>>(left.
m, n, left.
k, left.
a, left.
lda, b, ldb, c, ldc, left.
packed);
3133 inline constexpr std::size_t
dot_chunk {std::size_t {1} << 14U};
3141 template <
typename T>
3152 const std::size_t first {c *
dot_chunk};
3156 for (
const T&
sum : partial) {
3163 template <
typename T>
3168 inline constexpr std::size_t multiply_add_weight<std::complex<double>> {4};
3172 inline constexpr std::size_t multiply_add_weight<std::complex<float>> {4};
3177#ifdef NUMERICAL_AMX_COMPILED
3180 inline constexpr std::size_t amx_dot_least {std::size_t {1} << 15U};
3185 inline constexpr std::size_t amx_dot_most {std::size_t {1} << 18U};
3195 template <
typename T>
3202#ifdef NUMERICAL_AMX_COMPILED
3203 if constexpr (std::is_same_v<T, double>) {
3204 const std::size_t elements {rows * cols};
3205 if (cols <= dot_chunk && elements >= amx_dot_least && elements < amx_dot_most &&
gemm_path_current() == gemm_path::amx) {
3206 const amx_session on {};
3207 amx_dot_rows_f64(a, cols, rows, x, cols, y);
3219 constexpr std::size_t weight {multiply_add_weight<T>};
3221 parallel_for((rows + group - 1) / group, group * cols * weight, [&](std::size_t g) {
3222 const std::size_t first {g * group};
3223 row_dots(a + (first * cols), cols, std::min(group, rows - first), x, cols, y + first);
The matrix coprocessor of Apple's M-series chips (AMX), for the double matrix product.
numerical::buffer, the storage of the library's vectors, matrices and tensors: a std::vector whose el...
The storage of a product's packed operands, kept by the calling thread from one product to the next.
packing_storage(std::size_t a_count, std::size_t b_count)
Room for a_count packed elements of A and b_count of B.
static buffer< T > grown(buffer< T > storage, std::size_t count)
storage with at least count elements (it only grows, so a reuse writes nothing).
static buffer< T > & kept() noexcept
The storage this thread keeps between products.
T * b() const noexcept
B's packed elements.
static T * aligned(T *at) noexcept
line_aligned.
static constexpr std::size_t padding
Elements enough to reach a line.
packing_storage(packing_storage &&)=delete
static constexpr std::size_t kept_limit
The most elements a thread keeps between products (32 MiB).
T * a() const noexcept
A's packed elements.
packing_storage & operator=(packing_storage &&)=delete
packing_storage(const packing_storage &)=delete
packing_storage & operator=(const packing_storage &)=delete
A dense vector of T with value semantics.
A complex number of float or double, which gemm_split multiplies through the real kernel and the kern...
#define NUMERICAL_SCALAR_DEPTH
#define NUMERICAL_UNROLL_BLOCK
#define NUMERICAL_OUT_OF_LINE
constexpr std::size_t gemm_packed_threshold
Below this many multiply-adds the plain loop (gemm_rows, by blocks of rows held in registers) runs in...
bool gemm_path_usable(gemm_path path) noexcept
Whether path can run here.
C complex_dot_product(const C *x, const C *y, std::size_t count)
Σ x[i] · y[i] of complex numbers: on the x86-64 units with AVX2 (the AVX-512 path too) their fused ch...
void gemm_packed(std::size_t m, std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc, const T *a_packed=nullptr)
The packed product with the tile Kernel computes: for each block of B's columns and each panel of the...
constexpr std::size_t split_columns
The columns of B and C, and the depth, of one real product of gemm_split: its real operands stay a fe...
void gemm_pack_b(const T *b, std::size_t ldb, std::size_t kc, std::size_t cols, T *out)
Copies kc × cols of B (from row-major, leading dimension ldb) into slivers of Nr columns,...
bool plain_loop_pays(std::size_t m, std::size_t n, std::size_t k) noexcept
Whether a double product of m rows, n columns and depth k, past gemm_rows's threshold,...
const T * pack_left_for(std::size_t m, std::size_t k, const T *a, std::size_t lda, buffer< T > &storage)
A packed for Kernel into storage, in parallel by its blocks of rows, as gemm_packed packs it.
void gemm_pack_a(const T *a, std::size_t lda, std::size_t rows, std::size_t kc, T *out)
Copies rows × kc of A (from row-major, leading dimension lda) into slivers of Mr rows,...
void gemm_triangle(std::size_t n, std::size_t k, const double *a, std::size_t lda, const double *b, std::size_t ldb, double *c, std::size_t ldc, bool lower)
The triangle of the double product C = A·B (gemm_packed_triangle) on the tile of the path in use,...
bool split_by_parts(std::size_t m) noexcept
Whether gemm_split runs a product of m rows of A by four real products of each block's parts (split_p...
void narrow_block(std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc) noexcept
C += A·B for R rows and N columns of C, each sum in ascending order of the depth, as the plain loop's...
constexpr bool prefetches_next_row_of_c
Whether Kernel has the C tile of the next row of tiles prefetched while a tile computes (Kernel::pref...
void subtract_rows(double *y, std::size_t ldy, const double *f, std::size_t ldf, const double *x, std::size_t ldx, std::size_t terms, std::size_t count)
subtract_rows for double, the units chosen as subtract_products chooses them: each element the value ...
left_kernel
The tile a product's left operand is packed for (numerical::detail::pack_left): plain where it is not...
@ plain
gemm_rows, or a scalar whose products are not shared: A is read in place.
void gemm_pack_at(const T *at, std::size_t ldat, std::size_t rows, std::size_t kc, T *out)
The slivers of Mr rows of A (rows × kc), column by column, from its transpose at (kc × rows row-major...
void gemm_rows_rounded(std::size_t m, std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc)
C += A·B by the plain i-k-j loop, parallel over the rows of C. Past a depth of 1 (narrow_least_depth)...
T chunked_dot(const T *a, const T *b, std::size_t count)
Σ a[i] · b[i] for a plain dot product: chunks of dot_chunk elements in parallel, each by dot_product ...
bool amx_usable() noexcept
Whether this chip's AMX can be used: known family and a passing self-test, decided once.
left_kernel left_kernel_for(std::size_t, std::size_t, std::size_t) noexcept
The tile gemm runs a product of m × n by depth k on, for a left operand packed once for many products...
constexpr std::size_t gemm_kernel_speedup
The multiply-adds the register tile does in the time of one in work_per_thread's dependent chain (abo...
void exchange(buffer< T > &a, std::size_t n, std::size_t i, std::size_t j)
Exchanges rows i and j, then columns i and j, of the row-major n × n a: a similarity.
void gemm_large_complex(std::size_t m, std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc)
A complex product past the plain loop: on the vector tile on the portable path, whole complex product...
T operator_times(const T &a, const T &b)
a · b, as std::operator* computes it, in line. libc++'s complex product (the four products and their ...
constexpr std::size_t vector_speedup
The multiply-adds a vectorised inner loop does in the time of one in work_per_thread's dependent chai...
void gemm_at(std::size_t m, std::size_t n, std::size_t k, const T *at, std::size_t ldat, const T *b, std::size_t ldb, T *c, std::size_t ldc)
gemm_layouts with A given as Aᵀ (at, k × m, ldat apart).
constexpr std::size_t gemm_mr
The rows of C one register tile covers.
constexpr std::size_t narrow_group
The rows of C one block of gemm_rows takes together: each element a chain of multiply-adds that waits...
void add_product(double *y, const double &f, const double *x, std::size_t count)
y[i] += f · x[i] for double, as subtract_product.
gemm_path best_gemm_path(gemm_path most) noexcept
The most capable path that can run here, most at the highest.
void row_dots(const std::complex< double > *m, std::size_t ld, std::size_t rows, const std::complex< double > *x, std::size_t count, std::complex< double > *out)
row_dots for std::complex<double>, through complex_row_dots.
void subtract_products(double *y, const double *f, const double *x, std::size_t ldx, std::size_t terms, std::size_t count)
y[i] -= Σₜ f[t] · x[(t · ldx) + i] for double, the units chosen as subtract_product chooses them,...
constexpr std::size_t dot_chunk
The elements of one partial sum of chunked_dot: fixed, so the sum does not depend on the threads.
void gemm_with(const packed_left< T > &left, std::size_t n, const T *b, std::size_t ldb, T *c, std::size_t ldc)
C += A·B for the packed left (pack_left) and the k × n b, as gemm computes it, A read in place here,...
bool gemm_elsewhere(std::size_t m, std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc)
A product a faster path than the vector one takes, if any: none for this scalar.
void gemm_bt(std::size_t m, std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *bt, std::size_t ldbt, T *c, std::size_t ldc)
gemm_layouts with B given as Bᵀ (bt, n × k, ldbt apart).
const T * rows_from_transpose(const T *x, std::size_t ld, std::size_t rows, std::size_t cols, buffer< T > &scratch, std::size_t &ld_out)
A row-major rows × cols operand for the plain loop, copied into scratch from its transpose x (cols × ...
void gemm_split(std::size_t m, std::size_t n, std::size_t k, const Z *a, std::size_t lda, const Z *b, std::size_t ldb, Z *c, std::size_t ldc)
C += A·B for complex numbers through the real kernel, by blocks of split_columns columns and split_de...
void subtract_product(double *y, const double &f, const double *x, std::size_t count)
y[i] −= f · x[i] for double on the widest x86-64 units the matrix product's kernel may use (gemm_path...
constexpr std::size_t plain_loop_least_width
The fewest columns of a product plain_loop_pays sends to the plain loop on the x86-64 tiles: there an...
packed_left< T > pack_left(std::size_t m, std::size_t, std::size_t k, const T *a, std::size_t lda, buffer< T > &)
The m × k a packed once for products with it of about n columns each (gemm_with), in storage (grown a...
constexpr std::size_t split_kept
The most elements an operand of gemm_split keeps from one product to the next on a thread: 2 MiB of d...
T rounded_apart_add(T sum, T a, T b) noexcept
sum + a·b as the vector tile rounds it (vector_tile_kernel), so that the plain loop's elements are it...
bool plain_loop_suits(std::size_t, std::size_t, std::size_t) noexcept
plain_loop_pays for a product of T: double's alone, the one it was measured for.
constexpr std::size_t tasks_per_thread
The tasks a product's compute pass hands each thread at least, while its rows allow: a slower core th...
constexpr std::size_t split_depth
The depth of one real product of gemm_split (split_columns).
T * line_aligned(T *at) noexcept
The first element of at's run that starts a cache line (or at's nearest element towards one,...
std::atomic< std::uint8_t > & gemm_path_found() noexcept
The path gemm_path_current found last, packed with the cap it was found under (cap · 8 + path); 0xFF ...
constexpr bool gemm_shares(std::size_t m, std::size_t n, std::size_t k) noexcept
Whether the product's threads pay: below Kernel's serial_below multiply-adds, the product runs on the...
constexpr bool neon_compiled
Whether the NEON path's tile is compiled in: on every arm64 target GCC or Clang builds.
void parallel_for(std::size_t count, std::size_t work_per_index, const Body &body)
Runs body(i) for every i in [0, count).
void gemm_large(std::size_t m, std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc)
A product past the plain loop: on the AMX or x86-64 tiles where gemm_elsewhere takes it,...
constexpr std::size_t gemm_nr
The columns of C one register tile covers: 96 bytes of elements (12 double, 24 float),...
constexpr std::size_t gemm_mc
The rows of one task: an A block (gemm_mc × gemm_kc) stays in the L2 cache.
void split_expanded_block(std::size_t m, std::size_t nb, std::size_t kb, std::size_t pc, std::size_t jc, const Z *a, std::size_t lda, const Z *b, std::size_t ldb, typename Z::value_type *ar, typename Z::value_type *br, typename Z::value_type *cr)
One block of gemm_split (split_parts_block's) by one real product of its expanded operands,...
gemm_path gemm_path_current() noexcept
The path the next double product takes: the most capable one under the cap that runs here (each path'...
void gemm_large_layouts(std::size_t m, std::size_t n, std::size_t k, const double *a, std::size_t lda, const double *b, std::size_t ldb, double *c, std::size_t ldc)
A double product past the plain loop, B and A laid out as B and A say (gemm_layouts).
std::integral_constant< term_rounding, Mode > rounding_is
The rounding mode Mode as a type, which picks rounded_term's overload.
constexpr std::size_t packed_line
The cache line a packed operand starts.
constexpr std::size_t narrow_columns
The columns of C one block of gemm_rows keeps in registers over the whole depth.
const T * rows_in_place(const T *x, std::size_t ld, std::size_t &ld_out) noexcept
A row-major rows × cols operand for the plain loop, x itself (ld apart).
T rounded_term(rounding_is< term_rounding::compiler >, T sum, T a, T b) noexcept
sum + a·b as the compiler rounds it (rounded_apart_add).
std::array< T, gemm_mr *gemm_nr< T > > gemm_tile
constexpr std::size_t narrow_least_depth
The least depth at which rows of C wider than one block (narrow_columns) run by blocks in gemm_rows; ...
packed_left< T > left_rows(const packed_left< T > &left, std::size_t r0, std::size_t rows) noexcept
The rows [r0, r0 + rows) of left, r0 a multiple of left.granule: their slivers in its packing,...
constexpr std::size_t gemm_kc
The depth of one panel: a B sliver (gemm_kc × gemm_nr) stays in the L1 cache.
void gemm_pack_bt(const T *bt, std::size_t ldbt, std::size_t kc, std::size_t cols, T *out)
gemm_pack_b of B = Bᵀᵀ, from the row-major bt (cols × kc, leading dimension ldbt): each column of a s...
double dot_product(const double *x, const double *y, std::size_t count)
Σ x[i] · y[i] for double, as subtract_product chooses its units (in another order of summation there,...
void complex_scaled_add(C &y, const C &f, const C &x)
y += f · x of one complex number, its part products rounded apart as libc++'s operator computes them ...
void split_parts_block(std::size_t m, std::size_t nb, std::size_t kb, std::size_t pc, std::size_t jc, const Z *a, std::size_t lda, const Z *b, std::size_t ldb, typename Z::value_type *ar, typename Z::value_type *br, typename Z::value_type *cr)
One block of gemm_split ([Re C | Im C] at cr, m × nb each, += A·B, A m × kb and B kb × nb) by four re...
constexpr std::size_t row_group
The rows a thread takes of a matrix times a vector (matrix_times_vector).
void narrow_rows(std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc) noexcept
C += A·B for R whole rows of C of n columns: blocks of narrow_columns, the last narrower.
constexpr std::size_t split_parts_rows
The most rows of A for which gemm_split runs four real products of a block's parts on the AMX path (s...
constexpr std::size_t task_rows_cap(std::size_t) noexcept
The most rows of a task of Kernel over a panel of depth kc: its block of rows.
packed_left< T > read_in_place(std::size_t m, std::size_t k, const T *a, std::size_t lda) noexcept
The m × k a read in place by gemm_with, unpacked: each product packs it as gemm does.
constexpr std::size_t gemm_min_depth
Below this depth the plain loop runs too: each panel reads and writes its tiles of C once,...
split_operands< R > & split_storage()
The calling thread's split_operands. A product uses them whole on one thread, and the real product it...
void gemm_kernel(std::size_t kc, const T *ap, const T *bp, std::size_t ldb, gemm_tile< T > &tile)
tile += Aₚ·Bₚ over one panel, for any scalar.
void gemm_rows(std::size_t m, std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc)
C += A·B by the plain i-k-j loop (gemm_rows_rounded), each element rounded as the tile of the path in...
void gemm_packed_triangle(std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc, bool lower)
gemm_packed restricted to a triangle of the square C = A·B (n × n): its lower one (j ≤ i) where lower...
bool gemm_float_elsewhere(std::size_t m, std::size_t n, std::size_t k, const float *a, std::size_t lda, const float *b, std::size_t ldb, float *c, std::size_t ldc)
A float product on the AMX where it suits the product, on the NEON tile on arm64's fused paths,...
constexpr std::size_t gemm_nc
The columns of B packed at once, a whole number of slivers.
std::atomic< gemm_path > & gemm_path_cap() noexcept
The most capable path products may take, as numerical::limit_gemm_path set it: none is probed until a...
bool rows_fuse() noexcept
Whether the tile a double or float product past the plain loop runs on fuses each term (gemm_rows rou...
constexpr std::size_t gemm_slivers_per_task
The B slivers of one task.
void prefetch_next_row(const T *, std::size_t) noexcept
Nothing, for a tile that prefetches no C (prefetches_next_row_of_c).
constexpr std::size_t multiply_add_weight
The real multiply-adds one multiply-add of T weighs: 1 for a real number.
void gemm_layouts(std::size_t m, std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc)
gemm with either operand given as its transpose (A: a_rows or a_transposed, B: b_rows or b_transposed...
void gemm_atbt(std::size_t m, std::size_t n, std::size_t k, const T *at, std::size_t ldat, const T *bt, std::size_t ldbt, T *c, std::size_t ldc)
gemm_layouts with both operands given as their transposes.
void complex_row_dots(const C *m, std::size_t ld, std::size_t rows, const C *x, std::size_t count, C *out)
row_dots of complex numbers: each row by complex_dot_product, as chunked_dot sums a row.
term_rounding
How numerical::detail::gemm_rows rounds each term sum + a·b, as the tile of the path in use does: as ...
@ x86_fused
std::fma in functions built for AVX2 with FMA.
@ fused
std::fma, where every target has the instruction.
@ compiler
As the compiler rounds sum + a·b (rounded_apart_add).
bool gemm_double_elsewhere(std::size_t m, std::size_t n, std::size_t k, const double *a, std::size_t lda, const double *b, std::size_t ldb, double *c, std::size_t ldc)
A double product on the path in use (numerical::gemm_path_in_use), unless it is the portable one,...
void matrix_times_vector(const T *a, std::size_t rows, std::size_t cols, const T *x, T *y)
y = A x for the row-major rows × cols a: each element the dot product of its row with x,...
The NumericAL public API.
std::vector< T, detail::default_init_allocator< T > > buffer
The storage of vector, matrix and tensor, namely a std::vector whose buffer<T>(n) and resize(n) leave...
std::size_t max_threads()
The threads a kernel may use: the cap if one is set, else the hardware threads (starting none).
T sum(const tensor< T > &t)
The sum of every element, by pairwise halving (0 for an empty tensor).
gemm_path gemm_path_in_use() noexcept
The path double matrix products take.
gemm_path limit_gemm_path(gemm_path most) noexcept
Keeps double matrix products on most or a more portable path: the most capable path at or below it th...
gemm_path
The kernels a double matrix product can run on (a float one too on arm64), from the most portable....
@ avx512
AVX-512 with fused multiply-adds.
@ portable
The compiler's vectors (16 bytes on x86-64 wheels), as the compiler rounds c + a·b (see gemm).
@ avx2
AVX2 with fused multiply-adds.
@ amx
The Apple AMX coprocessor, fused multiply-adds; the products it does not suit on the NEON path's tile...
@ neon
arm64's vectors with fused multiply-adds, for double and float.
A minimal std::thread parallel-for for the hot kernels.
The native vector of float and double the kernels hold their lanes in (GNU vector extensions,...
#define NUMERICAL_ALWAYS_INLINE
A as gemm_packed reads it by default: row-major, m × k, its rows lda apart, packed by the tile.
static void pack(const T *a, std::size_t lda, std::size_t i0, std::size_t p0, std::size_t rows, std::size_t kc, T *out)
The rows from i0 of A, rows × kc from column p0, packed for Kernel.
static const T & at(const T *a, std::size_t lda, std::size_t i, std::size_t p) noexcept
A's element in row i and column p.
static const T * rows_of(const T *a, std::size_t lda, std::size_t, std::size_t, buffer< T > &, std::size_t &ld_out)
A row-major for the plain loop: itself.
A given as Aᵀ: row-major, k × m, its rows lda apart (C = Aᵀᵀ·B, no copy of the transpose).
static const T * rows_of(const T *at, std::size_t ldat, std::size_t rows, std::size_t cols, buffer< T > &scratch, std::size_t &ld_out)
A row-major for the plain loop, copied from at into scratch.
static const T & at(const T *at_, std::size_t ldat, std::size_t i, std::size_t p) noexcept
A's element in row i and column p: Aᵀ's in row p and column i.
static void pack(const T *at, std::size_t ldat, std::size_t i0, std::size_t p0, std::size_t rows, std::size_t kc, T *out)
The rows from i0 of A, rows × kc from column p0, packed as Kernel lays them out (gemm_pack_at).
B as gemm_packed reads it by default: row-major, k × n, its rows ldb apart.
static void pack(const T *b, std::size_t ldb, std::size_t p0, std::size_t j0, std::size_t kc, std::size_t cols, T *out)
The kc × cols block of B from row p0 and column j0 into slivers of Nr columns.
static constexpr bool in_place(std::size_t ldb) noexcept
Whether Kernel reads B's whole slivers in place.
static const T * rows_of(const T *x, std::size_t ld, std::size_t, std::size_t, buffer< T > &, std::size_t &ld_out)
The rows × cols operand row-major for the plain loop: x itself, ld apart.
static const T & at(const T *b, std::size_t ldb, std::size_t p, std::size_t j) noexcept
B's element in row p and column j.
B given as Bᵀ: row-major, n × k, its rows ldb apart (C = A·Bᵀᵀ, no copy of the transpose).
static const T & at(const T *bt, std::size_t ldbt, std::size_t p, std::size_t j) noexcept
B's element in row p and column j: Bᵀ's in row j and column p.
static constexpr bool in_place(std::size_t) noexcept
Never in place: a sliver's columns lie apart.
static const T * rows_of(const T *x, std::size_t ld, std::size_t rows, std::size_t cols, buffer< T > &scratch, std::size_t &ld_out)
The rows × cols operand row-major for the plain loop, copied from its transpose x (ld apart) into scr...
static void pack(const T *bt, std::size_t ldbt, std::size_t p0, std::size_t j0, std::size_t kc, std::size_t cols, T *out)
The kc × cols block of B from row p0 and column j0 (Bᵀ's rows from j0, its columns from p0) into sliv...
A left operand packed once for several products by it (an LU's −L₂₁ against blocks of its U₁₂): the t...
std::size_t lda
A's row distance.
std::size_t k
A's columns, the depth.
const T * packed
A packed for the tile (null where plain).
left_kernel kernel
The tile.
std::size_t granule
The rows of the tile's slivers: a block of rows from a multiple of it reads the packing.
The real operands of gemm_split, kept by each thread from one product to the next (split_storage): ma...
buffer< R > a
A's parts for a block of depth: [Re A | −Im A | Im A], or [Re A | −Im A].
Nothing to turn on around a task.
The vector path's tile: gemm_mr × gemm_nr elements of C in registers (gemm_kernel),...
static constexpr bool reads_b_in_place(std::size_t ldb) noexcept
Whether B's rows ldb apart are read in place: no farther apart than a packed sliver's (and a B that o...
static constexpr std::size_t speedup
The weight of its work (gemm_kernel_speedup).
static constexpr std::size_t b_in_place_rows
The most rows of A for which gemm_packed reads B in place at any distance between its rows (where rea...
static constexpr std::size_t depth
The panel depth.
static void tile(std::size_t kc, const T *ap, const T *bp, std::size_t ldb, T *c, std::size_t ldc, std::size_t rows, std::size_t cols)
C += Aₚ·Bₚ for one tile of C (rows × cols) over a panel of depth kc, B's rows ldb apart.
static constexpr std::size_t mr
The tile's rows.
static constexpr std::size_t serial_below
Every product with work shares it (gemm_shares).
static constexpr std::size_t nr
The tile's columns.
static void pack_a(const T *a, std::size_t lda, std::size_t rows, std::size_t kc, T *out)
A's slivers for this tile (gemm_pack_a).
static constexpr std::size_t block_rows
The x86-64 tiles of the double matrix product: AVX-512 and AVX2 with FMA, chosen at run time.