11// SPDX-License-Identifier: BSD-3-Clause
22#pragma once
33
4+ #include < atomic>
45#include < ttg.h>
56#include < ttg/config.h>
67#include " lapack.hh"
@@ -86,6 +87,87 @@ namespace potrf {
8687 hipblasDnrm2 (hipblas_handle (), size, buffer, 1 , norm);
8788 #endif
8889 }
90+
91+ #if defined(ENABLE_DEVICE_KERNEL)
92+ /* *
93+ * Small round-robin pool of pinned host / device pointer-array triples used
94+ * to build a single cublasDgemmBatched/hipblasDgemmBatched call from the
95+ * ttg::device::batch collected via ttg::device::coop (see make_gemm_batched
96+ * below). `num_slots` is sized generously so that an in-flight batch -- in
97+ * practice bounded by the number of concurrent device streams -- does not
98+ * reuse a slot before its batched kernel has completed. This is
99+ * illustrative example code, not a general-purpose device allocator.
100+ * Works (as a degenerate size-1 batch) even if the linked PaRSEC lacks
101+ * kernel-batching support; no TTG_HAVE_PARSEC_DEV_BATCH guard is needed.
102+ */
103+ template <typename T>
104+ struct GemmBatchPointerPool {
105+ explicit GemmBatchPointerPool (std::size_t max_batch_size, std::size_t num_slots = 8 )
106+ : max_batch_size(max_batch_size), slots(num_slots) {
107+ for (auto &s : slots) {
108+ s.hostA .resize (max_batch_size);
109+ s.hostB .resize (max_batch_size);
110+ s.hostC .resize (max_batch_size);
111+ #if defined(TTG_ENABLE_CUDA)
112+ cudaHostRegister (s.hostA .data (), max_batch_size * sizeof (const T *), cudaHostRegisterDefault);
113+ cudaHostRegister (s.hostB .data (), max_batch_size * sizeof (const T *), cudaHostRegisterDefault);
114+ cudaHostRegister (s.hostC .data (), max_batch_size * sizeof (T *), cudaHostRegisterDefault);
115+ cudaMalloc (&s.devA , max_batch_size * sizeof (const T *));
116+ cudaMalloc (&s.devB , max_batch_size * sizeof (const T *));
117+ cudaMalloc (&s.devC , max_batch_size * sizeof (T *));
118+ #elif defined(TTG_ENABLE_HIP)
119+ hipHostRegister (s.hostA .data (), max_batch_size * sizeof (const T *), hipHostRegisterDefault);
120+ hipHostRegister (s.hostB .data (), max_batch_size * sizeof (const T *), hipHostRegisterDefault);
121+ hipHostRegister (s.hostC .data (), max_batch_size * sizeof (T *), hipHostRegisterDefault);
122+ hipMalloc (&s.devA , max_batch_size * sizeof (const T *));
123+ hipMalloc (&s.devB , max_batch_size * sizeof (const T *));
124+ hipMalloc (&s.devC , max_batch_size * sizeof (T *));
125+ #endif
126+ }
127+ }
128+
129+ ~GemmBatchPointerPool () {
130+ for (auto &s : slots) {
131+ #if defined(TTG_ENABLE_CUDA)
132+ cudaHostUnregister (s.hostA .data ());
133+ cudaHostUnregister (s.hostB .data ());
134+ cudaHostUnregister (s.hostC .data ());
135+ cudaFree (s.devA );
136+ cudaFree (s.devB );
137+ cudaFree (s.devC );
138+ #elif defined(TTG_ENABLE_HIP)
139+ hipHostUnregister (s.hostA .data ());
140+ hipHostUnregister (s.hostB .data ());
141+ hipHostUnregister (s.hostC .data ());
142+ hipFree (s.devA );
143+ hipFree (s.devB );
144+ hipFree (s.devC );
145+ #endif
146+ }
147+ }
148+
149+ struct slot_t {
150+ std::vector<const T *> hostA, hostB;
151+ std::vector<T *> hostC;
152+ const T **devA = nullptr ;
153+ const T **devB = nullptr ;
154+ T **devC = nullptr ;
155+ };
156+
157+ /* acquire the next slot (round-robin); the caller fills hostA/hostB/hostC
158+ * (up to `count` entries) with this batch's device pointers and then
159+ * copies them to devA/devB/devC (e.g. via cudaMemcpyAsync/hipMemcpyAsync
160+ * on the current stream) before launching the batched kernel. */
161+ slot_t &acquire () {
162+ std::size_t idx = next.fetch_add (1 , std::memory_order_relaxed) % slots.size ();
163+ return slots[idx];
164+ }
165+
166+ std::size_t max_batch_size;
167+ std::vector<slot_t > slots;
168+ std::atomic<std::size_t > next{0 };
169+ };
170+ #endif // defined(ENABLE_DEVICE_KERNEL)
89171#endif // ENABLE_DEVICE_KERNEL
90172
91173 template <typename MatrixT>
@@ -530,14 +612,9 @@ namespace potrf {
530612 assert (tile_nk.rows () == tile_mn.cols ());
531613
532614 if (ttg::tracing ()) ttg::print (" GEMM(" , key, " )" );
533- #if defined(DEBUG_TILES_VALUES) && 0
534- // std::cout << "Before GEMM(" << key << "), A(" << M << ", " << K << ") is " << tile_mk << " and A(" << K << ", "
535- // << N << ") is " << tile_nk << " and A(" << M << ", " << N << ") is " << tile_mn;
536- #endif
537615
538616#ifdef DEBUG_TILES_VALUES
539617 std::array<T, 4 > norms; // input for tile_mk & tile_nk & tile_mn and output
540- // auto norms_s = ttg::make_scratch(norms.data(), ttg::scope::Allocate, norms.size());
541618 co_await ttg::device::select (tile_mk.buffer (), tile_nk.buffer (), tile_mn.buffer ());
542619
543620 /* compute the norms at input */
@@ -548,7 +625,6 @@ namespace potrf {
548625 co_await ttg::device::select (tile_mk.buffer (), tile_nk.buffer (), tile_mn.buffer ());
549626#endif // DEBUG_TILES_VALUES
550627
551- int device = ttg::device::current_device ();
552628 double alpha = -1.0 ;
553629 double beta = 1.0 ;
554630
@@ -570,7 +646,6 @@ namespace potrf {
570646 tile_mn.buffer ().current_device_ptr (), tile_mn.lda ());
571647#endif
572648
573-
574649#ifdef DEBUG_TILES_VALUES
575650 /* compute the norm at output */
576651 device_norm (tile_mn, &norms[3 ]);
@@ -639,6 +714,113 @@ namespace potrf {
639714#endif // defined(ENABLE_DEVICE_KERNEL)
640715 }
641716
717+ #if defined(ENABLE_DEVICE_KERNEL)
718+ /* *
719+ * Batching-enabled variant of make_gemm: collects the batch of sibling
720+ * GEMMs the runtime forms via ttg::device::coop/TT::set_batch_matcher and,
721+ * if this task is the batch's leader, submits ONE cublasDgemmBatched/
722+ * hipblasDgemmBatched call on behalf of every member. A batch of size 1
723+ * (e.g. because the linked PaRSEC lacks kernel-batching support, or no
724+ * siblings were ready) takes the exact same code path, so this is always
725+ * correct to use, just without the performance benefit in that case.
726+ * Does not support DEBUG_TILES_VALUES (see make_gemm for that).
727+ */
728+ template <typename MatrixT>
729+ auto make_gemm_batched (MatrixT& A,
730+ ttg::Edge<Key3, MatrixTile<typename MatrixT::element_type>>& input_disp, // From the dispatcher
731+ ttg::Edge<Key3, MatrixTile<typename MatrixT::element_type>>& input_mk, // from TRSM
732+ ttg::Edge<Key3, MatrixTile<typename MatrixT::element_type>>& input_nk, // from TRSM
733+ ttg::Edge<Key3, MatrixTile<typename MatrixT::element_type>>& input_mn, // from TRSM
734+ ttg::Edge<Key2, MatrixTile<typename MatrixT::element_type>>& output_trsm, // to TRSM
735+ ttg::Edge<Key3, MatrixTile<typename MatrixT::element_type>>& output_gemm,
736+ std::size_t max_batch_size = 32 ) {
737+ using T = typename MatrixT::element_type;
738+ auto ptr_pool = std::make_shared<GemmBatchPointerPool<T>>(max_batch_size);
739+ auto f = [=](const Key3& key, const MatrixTile<T>& tile_mk, const MatrixTile<T>& tile_nk, MatrixTile<T>&& tile_mn,
740+ std::tuple<ttg::Out<Key2, MatrixTile<T>>, ttg::Out<Key3, MatrixTile<T>>>& out) TASKRET {
741+ const int M = key[0 ];
742+ const int N = key[1 ];
743+ const int K = key[2 ];
744+ assert (M != N && M > K && N > K);
745+
746+ assert (tile_mk.cols () == tile_nk.cols ());
747+ assert (tile_mk.rows () == tile_mn.rows ());
748+ assert (tile_nk.rows () == tile_mn.cols ());
749+
750+ if (ttg::tracing ()) ttg::print (" GEMM(" , key, " )" );
751+
752+ co_await ttg::device::select (tile_mk.buffer (), tile_nk.buffer (), tile_mn.buffer ());
753+
754+ /* Collect the batch of GEMMs the runtime formed together with this task
755+ * (see set_batch_matcher below) and, if we're the batch's leader, submit
756+ * ONE batched kernel on behalf of every member. */
757+ auto batch = co_await ttg::device::coop<Key3>(tile_mk, tile_nk, tile_mn);
758+
759+ if (batch.is_leader ()) {
760+ const std::size_t nb = batch.size ();
761+ auto &slot = ptr_pool->acquire ();
762+ for (std::size_t i = 0 ; i < nb; ++i) {
763+ slot.hostA [i] = batch[i].template get <0 >().buffer ().current_device_ptr ();
764+ slot.hostB [i] = batch[i].template get <1 >().buffer ().current_device_ptr ();
765+ slot.hostC [i] = batch[i].template get <2 >().buffer ().current_device_ptr ();
766+ }
767+ double alpha = -1.0 ;
768+ double beta = 1.0 ;
769+ int mb = tile_mk.rows (), nnb = tile_nk.rows (), kb = tile_nk.cols ();
770+ #if defined(TTG_ENABLE_CUDA)
771+ cudaMemcpyAsync (slot.devA , slot.hostA .data (), nb * sizeof (const T *), cudaMemcpyHostToDevice,
772+ ttg::device::current_stream ());
773+ cudaMemcpyAsync (slot.devB , slot.hostB .data (), nb * sizeof (const T *), cudaMemcpyHostToDevice,
774+ ttg::device::current_stream ());
775+ cudaMemcpyAsync (slot.devC , slot.hostC .data (), nb * sizeof (T *), cudaMemcpyHostToDevice,
776+ ttg::device::current_stream ());
777+ cublasDgemmBatched (cublas_handle (), CUBLAS_OP_N , CUBLAS_OP_T , mb, nnb, kb, &alpha,
778+ slot.devA , tile_mk.lda (), slot.devB , tile_nk.lda (), &beta,
779+ slot.devC , tile_mn.lda (), (int )nb);
780+ #elif defined(TTG_ENABLE_HIP)
781+ hipMemcpyAsync (slot.devA , slot.hostA .data (), nb * sizeof (const T *), hipMemcpyHostToDevice,
782+ ttg::device::current_stream ());
783+ hipMemcpyAsync (slot.devB , slot.hostB .data (), nb * sizeof (const T *), hipMemcpyHostToDevice,
784+ ttg::device::current_stream ());
785+ hipMemcpyAsync (slot.devC , slot.hostC .data (), nb * sizeof (T *), hipMemcpyHostToDevice,
786+ ttg::device::current_stream ());
787+ hipblasDgemmBatched (hipblas_handle (), HIPBLAS_OP_N , HIPBLAS_OP_T , mb, nnb, kb, &alpha,
788+ slot.devA , tile_mk.lda (), slot.devB , tile_nk.lda (), &beta,
789+ slot.devC , tile_mn.lda (), (int )nb);
790+ #endif
791+ }
792+ // followers: the leader's batched call already covers our output tile.
793+
794+ // matching the non-batched GEMM, no explicit device::wait() is needed
795+ // here: nothing downstream needs this tile back on the host, so we
796+ // forward it directly (PaRSEC's own data-copy tracking, plus the fact
797+ // that the kernel and any subsequent transfers of this tile are
798+ // enqueued on the same device stream, take care of ordering).
799+ if (N == K + 1 ) {
800+ /* send the tile to trsm */
801+ if (ttg::tracing ()) ttg::print (" GEMM(" , key, " ): sending output to TRSM(" , Key2{M, N}, " )" );
802+ co_await ttg::device::send<0 >(Key2 (M, N), std::move (tile_mn), out);
803+ } else {
804+ /* send the tile to the next gemm */
805+ if (ttg::tracing ()) ttg::print (" GEMM(" , key, " ): sending output to GEMM(" , Key3{M, N, K + 1 }, " )" );
806+ co_await ttg::device::send<1 >(Key3 (M, N, K + 1 ), std::move (tile_mn), out);
807+ }
808+ };
809+ auto tt_gemm = ttg::make_tt<ES >(f, ttg::edges (input_mk, input_nk, ttg::fuse (input_disp, input_mn)),
810+ ttg::edges (output_trsm, output_gemm), " GEMM" , {" input_mk" , " input_kn" , " input_mn/dispatcher" },
811+ {" output_trsm" , " outout_gemm" });
812+ // cublasDgemmBatched/hipblasDgemmBatched require identical (m,n,k) and
813+ // leading dimensions across the whole batch; tiles sharing the same K
814+ // column in this tiling always have matching geometry, so that is a
815+ // sufficient compatibility test. Unconditionally safe to call: it is a
816+ // no-op unless the linked PaRSEC actually supports kernel batching.
817+ tt_gemm->set_batch_matcher (
818+ [](const Key3& head, const Key3& cand) { return head[2 ] == cand[2 ]; },
819+ max_batch_size);
820+ return tt_gemm;
821+ }
822+ #endif // defined(ENABLE_DEVICE_KERNEL)
823+
642824 template <typename T>
643825 auto make_dispatcher (ttg::Edge<Key2, MatrixTile<T>>& input, ttg::Edge<Key1, MatrixTile<T>>& to_potrf,
644826 ttg::Edge<Key2, MatrixTile<T>>& to_trsm, ttg::Edge<Key2, MatrixTile<T>>& to_syrk,
@@ -679,7 +861,7 @@ namespace potrf {
679861 template <typename MatrixT>
680862 auto make_potrf_ttg (MatrixT& A, ttg::Edge<Key2, MatrixTile<typename MatrixT::element_type>>& input,
681863 ttg::Edge<Key2, MatrixTile<typename MatrixT::element_type>>& output, bool defer_write,
682- bool enable_device_map = true ) {
864+ bool enable_device_map = true , bool enable_gemm_batching = false ) {
683865 using T = typename MatrixT::element_type;
684866 auto keymap1 = [&](const Key1& key) { return A.rank_of (key[0 ], key[0 ]); };
685867
@@ -744,15 +926,6 @@ namespace potrf {
744926 }
745927#endif // 0
746928
747- auto tt_gemm = make_gemm (A, disp_gemm, trsm_gemm_row, trsm_gemm_col, gemm_gemm, gemm_trsm, gemm_gemm);
748- tt_gemm->set_keymap (keymap3);
749- tt_gemm->set_defer_writer (defer_write);
750- #ifdef ENABLE_DEVICE_KERNEL
751- if (enable_device_map) {
752- tt_gemm->set_devicemap (devmap3);
753- }
754- #endif // 0
755-
756929 /* Priorities taken from DPLASMA */
757930 auto nt = A.cols ();
758931 tt_potrf->set_priomap ([nt](const Key1& key) { return ((nt - key[0 ]) * (nt - key[0 ]) * (nt - key[0 ])); });
@@ -761,10 +934,33 @@ namespace potrf {
761934 });
762935 tt_syrk->set_priomap (
763936 [nt](const Key2& key) { return ((nt - key[0 ]) * (nt - key[0 ]) * (nt - key[0 ]) + 3 * (key[0 ] - key[1 ])); });
764- tt_gemm->set_priomap ([nt](const Key3& key) {
765- return ((nt - key[0 ]) * (nt - key[0 ]) * (nt - key[0 ]) + 3 * ((2 * nt) - key[0 ] - key[1 ] - 3 ) * (key[0 ] - key[1 ]) +
766- 6 * (key[0 ] - key[2 ]));
767- });
937+
938+ // GEMM: pick the plain or batching-enabled implementation (driver's choice);
939+ // apply the common setup to whichever concrete TT gets constructed, then
940+ // erase to TTBase to store alongside the other TTs.
941+ auto configure_gemm = [&](auto tt) -> std::unique_ptr<ttg::TTBase> {
942+ tt->set_keymap (keymap3);
943+ tt->set_defer_writer (defer_write);
944+ #ifdef ENABLE_DEVICE_KERNEL
945+ if (enable_device_map) {
946+ tt->set_devicemap (devmap3);
947+ }
948+ #endif // 0
949+ tt->set_priomap ([nt](const Key3& key) {
950+ return ((nt - key[0 ]) * (nt - key[0 ]) * (nt - key[0 ]) + 3 * ((2 * nt) - key[0 ] - key[1 ] - 3 ) * (key[0 ] - key[1 ]) +
951+ 6 * (key[0 ] - key[2 ]));
952+ });
953+ return tt;
954+ };
955+ std::unique_ptr<ttg::TTBase> tt_gemm;
956+ #ifdef ENABLE_DEVICE_KERNEL
957+ if (enable_gemm_batching) {
958+ tt_gemm = configure_gemm (make_gemm_batched (A, disp_gemm, trsm_gemm_row, trsm_gemm_col, gemm_gemm, gemm_trsm, gemm_gemm));
959+ } else
960+ #endif // ENABLE_DEVICE_KERNEL
961+ {
962+ tt_gemm = configure_gemm (make_gemm (A, disp_gemm, trsm_gemm_row, trsm_gemm_col, gemm_gemm, gemm_trsm, gemm_gemm));
963+ }
768964
769965 auto ins = std::make_tuple (tt_dispatch->template in <0 >());
770966 auto outs = std::make_tuple (tt_potrf->template out <0 >());
0 commit comments