Skip to content

Commit d68c80f

Browse files
committed
Add optional libxsmm fast path for strided ToT micro-GEMMs (-DTA_LIBXSMM=ON)
Fetch+build libxsmm from source (no system install assumed) and route the small strided tensor-of-tensors GEMMs (ce+e, ce+ce, scale) through its JIT, falling back to vendor BLAS for shapes max(M,N,K)>64. Runtime toggle TA_LIBXSMM=0.
1 parent f20abfb commit d68c80f

9 files changed

Lines changed: 432 additions & 44 deletions

File tree

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ if (TA_STRIDED_DGEMM_COUNT)
154154
add_compile_definitions(TA_STRIDED_DGEMM_COUNT)
155155
endif()
156156

157+
option(TA_LIBXSMM
158+
"Fetch+build libxsmm from source and route small strided ToT micro-GEMMs (ce+e, ce+ce, scale) through its JIT fast path; falls back to vendor BLAS for shapes max(M,N,K)>64. Runtime toggle: TA_LIBXSMM=0"
159+
OFF)
160+
157161
option(TA_EXPERT "TiledArray Expert mode: disables automatically downloading or building dependencies" OFF)
158162

159163
redefaultable_option(TA_WERROR "Treat compiler warnings as errors when compiling TiledArray's own translation units (does not propagate to consumers of installed TiledArray targets)" OFF)
@@ -366,6 +370,9 @@ include(${PROJECT_SOURCE_DIR}/cmake/modules/FindOrFetchBTAS.cmake)
366370
if(TA_SCALAPACK)
367371
include(external/scalapackpp.cmake)
368372
endif()
373+
if(TA_LIBXSMM)
374+
include(external/libxsmm.cmake)
375+
endif()
369376

370377
# other optional deps:
371378
# 2. TTG

external/libxsmm.cmake

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
##
2+
## Fetch + build libxsmm from source, and expose it as the TiledArray_LIBXSMM
3+
## INTERFACE target. Enabled by -DTA_LIBXSMM=ON.
4+
##
5+
## libxsmm provides a JIT small-GEMM fast path for the strided tensor-of-tensors
6+
## micro-GEMMs (ce+e, ce+ce, scale). Unlike most TA deps, libxsmm's canonical
7+
## build is a GNU Makefile (not CMake), so this uses ExternalProject_Add with a
8+
## custom `make ... install` build command rather than CMAKE_ARGS.
9+
##
10+
## No system install is assumed: if libxsmm is not found via LIBXSMM_INSTALL_DIR
11+
## (an optional hint), it is cloned and built from source under
12+
## ${FETCHCONTENT_BASE_DIR}. There is intentionally NO TA_LIBXSMM_ROOT knob.
13+
##
14+
15+
# Optional: reuse a pre-built libxsmm ONLY if the user explicitly points at one
16+
# via -DLIBXSMM_INSTALL_DIR=... We deliberately do NOT search default system
17+
# paths (no NO_DEFAULT_PATH omission): a stray /usr/local install must never be
18+
# picked up silently. Absent an explicit hint, libxsmm is always fetched+built.
19+
# clear any stale value cached by a prior configure (e.g. before this guard)
20+
unset(_LIBXSMM_INSTALL_DIR CACHE)
21+
set(_LIBXSMM_PREBUILT _LIBXSMM_PREBUILT-NOTFOUND)
22+
if (DEFINED LIBXSMM_INSTALL_DIR)
23+
find_path(_LIBXSMM_PREBUILT NAMES include/libxsmm.h lib/libxsmm.a
24+
HINTS ${LIBXSMM_INSTALL_DIR} NO_DEFAULT_PATH)
25+
endif ()
26+
27+
if (_LIBXSMM_PREBUILT)
28+
29+
set(_LIBXSMM_INSTALL_DIR ${_LIBXSMM_PREBUILT})
30+
message(STATUS "libxsmm found at ${_LIBXSMM_INSTALL_DIR}")
31+
32+
elseif (TA_EXPERT)
33+
34+
message("** libxsmm was not found")
35+
message(STATUS "** Downloading and building libxsmm is explicitly disabled in EXPERT mode")
36+
message(FATAL_ERROR "** Either provide a pre-built libxsmm via -DLIBXSMM_INSTALL_DIR=... or disable -DTA_LIBXSMM=OFF")
37+
38+
else ()
39+
40+
include(ExternalProject)
41+
42+
# libxsmm is a C library; make sure CMAKE_C_COMPILER is configured
43+
enable_language(C)
44+
45+
set(EXTERNAL_SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/libxsmm-src)
46+
set(_LIBXSMM_INSTALL_DIR ${FETCHCONTENT_BASE_DIR}/libxsmm-install)
47+
48+
if (NOT LIBXSMM_URL)
49+
set(LIBXSMM_URL https://github.com/libxsmm/libxsmm.git)
50+
endif (NOT LIBXSMM_URL)
51+
if (NOT LIBXSMM_TAG)
52+
set(LIBXSMM_TAG ${TA_TRACKED_LIBXSMM_TAG})
53+
endif (NOT LIBXSMM_TAG)
54+
55+
message("** Will clone libxsmm from ${LIBXSMM_URL}")
56+
57+
# Compiler for libxsmm's sub-make. libxsmm builds with -target
58+
# <arch>-apple-macos, which makes a bare CommandLineTools clang stop
59+
# auto-injecting the macOS SDK sysroot, so it cannot find system headers
60+
# (pthread.h) or libSystem at link time (and CMAKE_OSX_SYSROOT is often
61+
# empty). On Apple, build libxsmm with the /usr/bin/{cc,c++} xcrun shims,
62+
# which always resolve the active SDK for both compile and link; the
63+
# resulting libxsmm.a is C-ABI-compatible with the rest of TiledArray.
64+
# Elsewhere, honor the project's configured compilers.
65+
if (APPLE)
66+
set(_libxsmm_cc /usr/bin/cc)
67+
set(_libxsmm_cxx /usr/bin/c++)
68+
else ()
69+
set(_libxsmm_cc ${CMAKE_C_COMPILER})
70+
set(_libxsmm_cxx ${CMAKE_CXX_COMPILER})
71+
endif ()
72+
73+
# libxsmm Make knobs:
74+
# STATIC=1 build libxsmm.a (we link the archive into tiledarray)
75+
# FORTRAN=0 skip the Fortran interface (no gfortran needed)
76+
# BLAS=0 do not wrap an external BLAS (we only use the JIT SMM path,
77+
# and TA already links its own BLAS); avoids a second BLAS dep
78+
# PREFIX=... install headers+lib into our private prefix
79+
set(LIBXSMM_BUILD_BYPRODUCTS "${_LIBXSMM_INSTALL_DIR}/lib/libxsmm.a")
80+
message(STATUS "custom target libxsmm is expected to build these byproducts: ${LIBXSMM_BUILD_BYPRODUCTS}")
81+
82+
ExternalProject_Add(libxsmm
83+
PREFIX ${FETCHCONTENT_BASE_DIR}
84+
STAMP_DIR ${FETCHCONTENT_BASE_DIR}/libxsmm-ep-artifacts
85+
TMP_DIR ${FETCHCONTENT_BASE_DIR}/libxsmm-ep-artifacts # in case CMAKE_INSTALL_PREFIX is not writable
86+
#--Download step--------------
87+
DOWNLOAD_DIR ${EXTERNAL_SOURCE_DIR}
88+
GIT_REPOSITORY ${LIBXSMM_URL}
89+
GIT_TAG ${LIBXSMM_TAG}
90+
#--Configure step------------- (none: libxsmm uses a plain Makefile)
91+
SOURCE_DIR ${EXTERNAL_SOURCE_DIR}
92+
UPDATE_DISCONNECTED 1
93+
BUILD_IN_SOURCE 1
94+
CONFIGURE_COMMAND ""
95+
#--Build step----------------- build + install in one make invocation
96+
BUILD_COMMAND make -j6 STATIC=1 FORTRAN=0 BLAS=0
97+
CC=${_libxsmm_cc} CXX=${_libxsmm_cxx} AR=${CMAKE_AR}
98+
PREFIX=${_LIBXSMM_INSTALL_DIR} install
99+
BUILD_BYPRODUCTS ${LIBXSMM_BUILD_BYPRODUCTS}
100+
#--Install step--------------- (done by BUILD_COMMAND above)
101+
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "libxsmm installed to ${_LIBXSMM_INSTALL_DIR}"
102+
#--Custom targets-------------
103+
STEP_TARGETS build
104+
)
105+
106+
# the include dir must exist at configure time so the INTERFACE target's
107+
# BUILD_INTERFACE include path validates (it is populated at build time)
108+
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory "${_LIBXSMM_INSTALL_DIR}/include")
109+
110+
# build libxsmm before any TiledArray translation unit links
111+
add_dependencies(External-tiledarray libxsmm)
112+
113+
endif (_LIBXSMM_INSTALL_DIR)
114+
115+
# Synthetic target carrying the include dir, the static archive, and the gating
116+
# define. PUBLIC propagation (via _TILEDARRAY_DEPENDENCIES) makes
117+
# TILEDARRAY_HAS_LIBXSMM + the include path visible to consumers (e.g. MPQC).
118+
add_library(TiledArray_LIBXSMM INTERFACE)
119+
set_target_properties(TiledArray_LIBXSMM
120+
PROPERTIES
121+
INTERFACE_INCLUDE_DIRECTORIES
122+
"$<BUILD_INTERFACE:${_LIBXSMM_INSTALL_DIR}/include>;$<INSTALL_INTERFACE:include>"
123+
INTERFACE_LINK_LIBRARIES
124+
"${_LIBXSMM_INSTALL_DIR}/lib/libxsmm.a;${CMAKE_DL_LIBS}"
125+
INTERFACE_COMPILE_DEFINITIONS
126+
"TILEDARRAY_HAS_LIBXSMM"
127+
)
128+
129+
install(TARGETS TiledArray_LIBXSMM EXPORT tiledarray COMPONENT tiledarray)

external/versions.cmake

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ set(TA_TRACKED_BTAS_PREVIOUS_TAG 7e64fbad97c76f316f313f4c8ed3fca5445da15f)
2323
set(TA_TRACKED_LIBRETT_TAG 6eed30d4dd2a5aa58840fe895dcffd80be7fbece)
2424
set(TA_TRACKED_LIBRETT_PREVIOUS_TAG 354e0ccee54aeb2f191c3ce2c617ebf437e49d83)
2525

26+
# libxsmm: pin a recent `main` commit, NOT the 1.17 release tag (2021). The
27+
# modern dispatch API used by TiledArray/math/libxsmm_gemm.h
28+
# (libxsmm_dispatch_gemm/libxsmm_create_gemm_shape/libxsmm_gemm_param) does not
29+
# exist in 1.17, and 1.17 predates most of libxsmm's AArch64/Apple-Silicon JIT
30+
# work. This SHA is the version validated on Apple M2 (reports as "1.17-3808").
31+
set(TA_TRACKED_LIBXSMM_TAG c14cbc6f8bc7964f8c5190a3a16b8cace03e5889)
32+
set(TA_TRACKED_LIBXSMM_PREVIOUS_TAG c14cbc6f8bc7964f8c5190a3a16b8cace03e5889)
33+
2634
set(TA_TRACKED_UMPIRE-CXX-ALLOCATOR_TAG 1ba7f5f0aa99438826dd1c6bc1cd396080b9d608)
2735
set(TA_TRACKED_UMPIRE-CXX-ALLOCATOR_PREVIOUS_TAG 0f8144f19897766d0f117f7353221d4e3b8b1178)
2836

src/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ TiledArray/external/madness.h
135135
TiledArray/host/env.h
136136
TiledArray/math/blas.h
137137
TiledArray/math/gemm_helper.h
138+
TiledArray/math/libxsmm_gemm.h
138139
TiledArray/math/outer.h
139140
TiledArray/math/parallel_gemm.h
140141
TiledArray/math/partial_reduce.h
@@ -215,6 +216,7 @@ set(TILEDARRAY_SOURCE_FILES
215216
TiledArray/einsum/index.cpp
216217
TiledArray/expressions/permopt.cpp
217218
TiledArray/host/env.cpp
219+
TiledArray/math/libxsmm_gemm.cpp
218220
TiledArray/math/linalg/basic.cpp
219221
TiledArray/math/linalg/rank-local.cpp
220222
TiledArray/tensor/print.cpp
@@ -303,6 +305,9 @@ endif(TILEDARRAY_HAS_CUDA OR TILEDARRAY_HAS_HIP)
303305
if( TARGET TiledArray_SCALAPACK )
304306
list(APPEND _TILEDARRAY_DEPENDENCIES TiledArray_SCALAPACK)
305307
endif()
308+
if( TARGET TiledArray_LIBXSMM )
309+
list(APPEND _TILEDARRAY_DEPENDENCIES TiledArray_LIBXSMM)
310+
endif()
306311
list(APPEND _TILEDARRAY_DEPENDENCIES "${LAPACK_LIBRARIES}")
307312

308313
if( TARGET ttg-parsec )
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/// \file libxsmm_gemm.cpp
2+
/// The ONLY translation unit that includes <libxsmm.h>. Isolating the libxsmm
3+
/// include here keeps its macros (libxsmm_macros.h) from leaking into any
4+
/// TiledArray header. When TILEDARRAY_HAS_LIBXSMM is undefined, libxsmm_gemm_le64
5+
/// is still defined here as a `return false` stub so callers link unconditionally.
6+
7+
#include "TiledArray/math/libxsmm_gemm.h"
8+
9+
#ifdef TILEDARRAY_HAS_LIBXSMM
10+
#include <cstdlib>
11+
#include <cstring>
12+
#include <mutex>
13+
#include <libxsmm.h>
14+
#endif
15+
16+
namespace TiledArray::detail {
17+
18+
/// max(M,N,K) cutoff above which we keep using the vendor BLAS.
19+
static constexpr std::int64_t libxsmm_gemm_max_dim = 64;
20+
21+
#ifdef TILEDARRAY_HAS_LIBXSMM
22+
/// Runtime master switch for the libxsmm fast path. Even in a libxsmm-enabled
23+
/// build, exporting `TA_LIBXSMM=0` (also accepts `off`/`OFF`/`false`/`no`)
24+
/// routes EVERY strided micro-GEMM back through the vendor BLAS path, i.e.
25+
/// libxsmm_gemm_le64() returns false for all shapes. Unset or any other value
26+
/// => libxsmm ON. Read from the environment once, on first use, and cached.
27+
static bool libxsmm_runtime_enabled() {
28+
static const bool enabled = [] {
29+
const char* v = std::getenv("TA_LIBXSMM");
30+
if (v == nullptr || *v == '\0') return true; // default ON when compiled in
31+
return !(std::strcmp(v, "0") == 0 || std::strcmp(v, "off") == 0 ||
32+
std::strcmp(v, "OFF") == 0 || std::strcmp(v, "false") == 0 ||
33+
std::strcmp(v, "no") == 0);
34+
}();
35+
return enabled;
36+
}
37+
#endif
38+
39+
bool libxsmm_gemm_le64(bool trans_a, bool trans_b, std::int64_t m,
40+
std::int64_t n, std::int64_t k, double alpha,
41+
const double* a, std::int64_t lda, const double* b,
42+
std::int64_t ldb, double beta, double* c,
43+
std::int64_t ldc) {
44+
#ifdef TILEDARRAY_HAS_LIBXSMM
45+
// Runtime master switch: TA_LIBXSMM=0 sends everything back to vendor BLAS.
46+
if (!libxsmm_runtime_enabled()) return false;
47+
// libxsmm only for small shapes; max(M,N,K) <= 64.
48+
if (m > libxsmm_gemm_max_dim || n > libxsmm_gemm_max_dim ||
49+
k > libxsmm_gemm_max_dim)
50+
return false;
51+
// libxsmm SMM has no alpha and only beta in {0,1} (LIBXSMM_GEMM_NO_BYPASS).
52+
if (alpha != 1.0) return false;
53+
if (beta != 0.0 && beta != 1.0) return false;
54+
55+
static std::once_flag init_flag;
56+
std::call_once(init_flag, [] {
57+
// libxsmm's own verbose dispatch/JIT stats are part of the profiling
58+
// result, so fold them into TA_PROFILE: when profiling is on and the user
59+
// has NOT pinned LIBXSMM_VERBOSE explicitly, enable libxsmm verbosity here,
60+
// BEFORE libxsmm_init() parses the environment. TA_PROFILE>=1 -> a concise
61+
// exit summary (version + registry "gemm=<n>" kernel count); TA_PROFILE>=2
62+
// -> verbose per-kernel JIT events. Must run before libxsmm_init().
63+
if (std::getenv("LIBXSMM_VERBOSE") == nullptr) {
64+
const char* p = std::getenv("TA_PROFILE");
65+
const int lvl = (p != nullptr) ? std::atoi(p) : 0;
66+
if (lvl >= 2)
67+
setenv("LIBXSMM_VERBOSE", "3", /*overwrite=*/0);
68+
else if (lvl >= 1)
69+
setenv("LIBXSMM_VERBOSE", "2", /*overwrite=*/0);
70+
}
71+
libxsmm_init();
72+
});
73+
74+
// Mirror blas::gemm's row-major -> col-major mapping: it realizes the result
75+
// as a column-major GEMM (op_b, op_a, n, m, k) with operands (b, a) swapped.
76+
// libxsmm is column-major, so: A'=b (ld=ldb), B'=a (ld=lda), dims (n, m, k),
77+
// TRANS_A from op_b, TRANS_B from op_a.
78+
const libxsmm_bitfield flags =
79+
(trans_b ? LIBXSMM_GEMM_FLAG_TRANS_A : 0) |
80+
(trans_a ? LIBXSMM_GEMM_FLAG_TRANS_B : 0) |
81+
(beta == 0.0 ? LIBXSMM_GEMM_FLAG_BETA_0 : 0);
82+
83+
const libxsmm_gemm_shape shape = libxsmm_create_gemm_shape(
84+
static_cast<libxsmm_blasint>(n), static_cast<libxsmm_blasint>(m),
85+
static_cast<libxsmm_blasint>(k), static_cast<libxsmm_blasint>(ldb),
86+
static_cast<libxsmm_blasint>(lda), static_cast<libxsmm_blasint>(ldc),
87+
LIBXSMM_DATATYPE_F64, LIBXSMM_DATATYPE_F64, LIBXSMM_DATATYPE_F64,
88+
LIBXSMM_DATATYPE_F64);
89+
90+
const libxsmm_gemmfunction kernel = libxsmm_dispatch_gemm(
91+
shape, flags, static_cast<libxsmm_bitfield>(LIBXSMM_GEMM_PREFETCH_NONE));
92+
if (kernel == nullptr) return false; // shape not JIT-able -> fall back
93+
94+
libxsmm_gemm_param param;
95+
std::memset(&param, 0, sizeof param);
96+
param.a.primary = const_cast<double*>(b); // A' = b
97+
param.b.primary = const_cast<double*>(a); // B' = a
98+
param.c.primary = c;
99+
kernel(&param);
100+
return true;
101+
#else
102+
(void)trans_a; (void)trans_b; (void)m; (void)n; (void)k; (void)alpha;
103+
(void)a; (void)lda; (void)b; (void)ldb; (void)beta; (void)c; (void)ldc;
104+
return false;
105+
#endif
106+
}
107+
108+
} // namespace TiledArray::detail

src/TiledArray/math/libxsmm_gemm.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#ifndef TILEDARRAY_MATH_LIBXSMM_GEMM_H__INCLUDED
2+
#define TILEDARRAY_MATH_LIBXSMM_GEMM_H__INCLUDED
3+
4+
/// \file libxsmm_gemm.h
5+
/// Optional libxsmm fast path for small strided tensor-of-tensors micro-GEMMs.
6+
/// This header is DECLARATION-ONLY: the implementation lives in libxsmm_gemm.cpp,
7+
/// which is the single translation unit that includes <libxsmm.h>. Keeping the
8+
/// libxsmm include out of this header is deliberate -- <libxsmm.h> pulls in
9+
/// libxsmm_macros.h, whose macros otherwise leak into TiledArray headers that
10+
/// transitively include this one (e.g. arena_einsum.h -> ... -> math/vector_op.h,
11+
/// breaking detail::is_scalar_v). Callers just see a plain function.
12+
13+
#include <cstdint>
14+
15+
// N.B. namespace TiledArray::detail (NOT TiledArray::math::detail): introducing
16+
// a TiledArray::math::detail namespace would hijack unqualified `detail::` name
17+
// lookup inside TiledArray::math headers (e.g. vector_op.h's detail::is_scalar_v,
18+
// which lives in TiledArray::detail), breaking their compilation.
19+
namespace TiledArray::detail {
20+
21+
/// Computes C(m x n) [+]= alpha * op_a(A) . op_b(B) in **row-major** layout with
22+
/// leading dims lda/ldb/ldc, i.e. exactly TiledArray::math::blas::gemm (double)
23+
/// semantics. \p trans_a / \p trans_b are the transpose flags (true == Trans).
24+
///
25+
/// \return true iff libxsmm performed the GEMM. Returns false (caller must fall
26+
/// back to blas::gemm) when: built without libxsmm (TILEDARRAY_HAS_LIBXSMM
27+
/// undefined), the runtime switch `TA_LIBXSMM=0` is set, max(m,n,k) > 64,
28+
/// alpha != 1, beta not in {0,1}, or libxsmm could not JIT this shape.
29+
bool libxsmm_gemm_le64(bool trans_a, bool trans_b, std::int64_t m,
30+
std::int64_t n, std::int64_t k, double alpha,
31+
const double* a, std::int64_t lda, const double* b,
32+
std::int64_t ldb, double beta, double* c,
33+
std::int64_t ldc);
34+
35+
} // namespace TiledArray::detail
36+
37+
#endif // TILEDARRAY_MATH_LIBXSMM_GEMM_H__INCLUDED

0 commit comments

Comments
 (0)