@@ -93,10 +93,13 @@ namespace potrf {
9393 * Small round-robin pool of pinned host / device pointer-array triples used
9494 * to build a single cublasDgemmBatched/hipblasDgemmBatched call from the
9595 * 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.
96+ * below). A larger `num_slots` reduces contention (how often acquire()
97+ * blocks, see below), but correctness does not depend on sizing it to
98+ * exceed the number of concurrent in-flight batches: acquire()/release()
99+ * use a per-slot event to block a new leader until the previous occupant's
100+ * batched kernel/copies have actually completed, so a slot is never reused
101+ * while still in flight. This is illustrative example code, not a
102+ * general-purpose device allocator.
100103 * Works (as a degenerate size-1 batch) even if the linked PaRSEC lacks
101104 * kernel-batching support; no TTG_HAVE_PARSEC_DEV_BATCH guard is needed.
102105 */
@@ -115,13 +118,15 @@ namespace potrf {
115118 cudaMalloc (&s.devA , max_batch_size * sizeof (const T *));
116119 cudaMalloc (&s.devB , max_batch_size * sizeof (const T *));
117120 cudaMalloc (&s.devC , max_batch_size * sizeof (T *));
121+ cudaEventCreateWithFlags (&s.ready , cudaEventDisableTiming);
118122#elif defined(TTG_ENABLE_HIP)
119123 hipHostRegister (s.hostA .data (), max_batch_size * sizeof (const T *), hipHostRegisterDefault);
120124 hipHostRegister (s.hostB .data (), max_batch_size * sizeof (const T *), hipHostRegisterDefault);
121125 hipHostRegister (s.hostC .data (), max_batch_size * sizeof (T *), hipHostRegisterDefault);
122126 hipMalloc (&s.devA , max_batch_size * sizeof (const T *));
123127 hipMalloc (&s.devB , max_batch_size * sizeof (const T *));
124128 hipMalloc (&s.devC , max_batch_size * sizeof (T *));
129+ hipEventCreateWithFlags (&s.ready , hipEventDisableTiming);
125130#endif
126131 }
127132 }
@@ -135,13 +140,15 @@ namespace potrf {
135140 cudaFree (s.devA );
136141 cudaFree (s.devB );
137142 cudaFree (s.devC );
143+ cudaEventDestroy (s.ready );
138144#elif defined(TTG_ENABLE_HIP)
139145 hipHostUnregister (s.hostA .data ());
140146 hipHostUnregister (s.hostB .data ());
141147 hipHostUnregister (s.hostC .data ());
142148 hipFree (s.devA );
143149 hipFree (s.devB );
144150 hipFree (s.devC );
151+ hipEventDestroy (s.ready );
145152#endif
146153 }
147154 }
@@ -152,15 +159,55 @@ namespace potrf {
152159 const T **devA = nullptr ;
153160 const T **devB = nullptr ;
154161 T **devC = nullptr ;
162+ #if defined(TTG_ENABLE_CUDA)
163+ cudaEvent_t ready = nullptr ;
164+ #elif defined(TTG_ENABLE_HIP)
165+ hipEvent_t ready = nullptr ;
166+ #endif
167+ // whether `ready` was ever recorded (i.e. this slot has a prior
168+ // occupant to wait for); false only before this slot's first release().
169+ bool ready_pending = false ;
155170 };
156171
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. */
172+ /* acquire the next slot (round-robin). Blocks (host-side) until the
173+ * previous occupant's batched kernel/copies -- which read this exact
174+ * slot's host/device pointer buffers -- have completed, so the caller
175+ * can safely overwrite hostA/hostB/hostC without racing an in-flight
176+ * H2D copy of the old contents. This host-side wait, not just device-
177+ * stream ordering, is required because the race is the CPU overwriting
178+ * pinned host memory while the GPU DMA engine may still be reading it;
179+ * ordering only the *device*-side work (e.g. cudaStreamWaitEvent) would
180+ * not protect the CPU write. The caller fills hostA/hostB/hostC (up to
181+ * `count` entries) with this batch's device pointers, copies them to
182+ * devA/devB/devC (e.g. via cudaMemcpyAsync/hipMemcpyAsync on the current
183+ * stream), launches the batched kernel, and must call release() with
184+ * that same stream so the next acquire() of this slot knows what to
185+ * wait for. */
161186 slot_t &acquire () {
162187 std::size_t idx = next.fetch_add (1 , std::memory_order_relaxed) % slots.size ();
163- return slots[idx];
188+ auto &slot = slots[idx];
189+ if (slot.ready_pending ) {
190+ #if defined(TTG_ENABLE_CUDA)
191+ cudaEventSynchronize (slot.ready );
192+ #elif defined(TTG_ENABLE_HIP)
193+ hipEventSynchronize (slot.ready );
194+ #endif
195+ }
196+ return slot;
197+ }
198+
199+ /* mark `slot` as in-use until every operation enqueued on `stream` so far
200+ * (in particular, the H2D copies and the batched kernel reading it) has
201+ * completed; must be called by the leader right after those are
202+ * enqueued, before the slot can be safely acquire()'d again. */
203+ template <typename Stream>
204+ void release (slot_t &slot, Stream stream) {
205+ #if defined(TTG_ENABLE_CUDA)
206+ cudaEventRecord (slot.ready , stream);
207+ #elif defined(TTG_ENABLE_HIP)
208+ hipEventRecord (slot.ready , stream);
209+ #endif
210+ slot.ready_pending = true ;
164211 }
165212
166213 std::size_t max_batch_size;
@@ -777,6 +824,7 @@ namespace potrf {
777824 cublasDgemmBatched (cublas_handle (), CUBLAS_OP_N , CUBLAS_OP_T , mb, nnb, kb, &alpha,
778825 slot.devA , tile_mk.lda (), slot.devB , tile_nk.lda (), &beta,
779826 slot.devC , tile_mn.lda (), (int )nb);
827+ ptr_pool->release (slot, ttg::device::current_stream ());
780828#elif defined(TTG_ENABLE_HIP)
781829 hipMemcpyAsync (slot.devA , slot.hostA .data (), nb * sizeof (const T *), hipMemcpyHostToDevice,
782830 ttg::device::current_stream ());
@@ -787,6 +835,7 @@ namespace potrf {
787835 hipblasDgemmBatched (hipblas_handle (), HIPBLAS_OP_N , HIPBLAS_OP_T , mb, nnb, kb, &alpha,
788836 slot.devA , tile_mk.lda (), slot.devB , tile_nk.lda (), &beta,
789837 slot.devC , tile_mn.lda (), (int )nb);
838+ ptr_pool->release (slot, ttg::device::current_stream ());
790839#endif
791840 }
792841 // followers: the leader's batched call already covers our output tile.
@@ -810,12 +859,24 @@ namespace potrf {
810859 ttg::edges (output_trsm, output_gemm), " GEMM" , {" input_mk" , " input_kn" , " input_mn/dispatcher" },
811860 {" output_trsm" , " outout_gemm" });
812861 // 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.
862+ // leading dimensions across the whole batch. Sharing the same K only
863+ // guarantees a common k (tile_nk.cols(), fixed by the K-th column tile);
864+ // M and N range independently over all tile indices > K, and the last
865+ // row tile of the matrix is smaller than the nominal tile size whenever
866+ // A.rows_in_matrix() is not an exact multiple of A.rows_in_tile(), so two
867+ // same-K candidates can still disagree on tile_mk.rows()/tile_nk.rows().
868+ // Compare the actual (boundary-aware) tile row-counts for M and N too --
869+ // a constant-time index computation, no data access needed.
870+ auto tile_rows_at = [rows = A.rows (), tile_rows = A.rows_in_tile (), m = A.rows_in_matrix ()](std::size_t idx) {
871+ return (idx < rows - 1 ) ? tile_rows : m - idx * tile_rows;
872+ };
873+ // Unconditionally safe to call: it is a no-op unless the linked PaRSEC
874+ // actually supports kernel batching.
817875 tt_gemm->set_batch_matcher (
818- [](const Key3& head, const Key3& cand) { return head[2 ] == cand[2 ]; },
876+ [tile_rows_at](const Key3& head, const Key3& cand) {
877+ return head[2 ] == cand[2 ] && tile_rows_at (head[0 ]) == tile_rows_at (cand[0 ]) &&
878+ tile_rows_at (head[1 ]) == tile_rows_at (cand[1 ]);
879+ },
819880 max_batch_size);
820881 return tt_gemm;
821882 }
0 commit comments