(git:d077e66)
Loading...
Searching...
No Matches
dbm_miniapp.c
Go to the documentation of this file.
1/*----------------------------------------------------------------------------*/
2/* CP2K: A general program to perform molecular dynamics simulations */
3/* Copyright 2000-2026 CP2K developers group <https://cp2k.org> */
4/* */
5/* SPDX-License-Identifier: BSD-3-Clause */
6/*----------------------------------------------------------------------------*/
7#include "dbm_library.h"
8#include "dbm_matrix.h"
9
10#include "../mpiwrap/cp_mpi.h"
13
14#include <assert.h>
15#include <limits.h>
16#include <omp.h>
17#include <stdint.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21
22/*******************************************************************************
23 * \brief Wrapper for printf, passed to dbm_library_print_stats.
24 * \author Ole Schuett
25 ******************************************************************************/
26static void print_func(const char *msg, int msglen, int output_unit) {
27 (void)msglen; // mark used
28 if (output_unit == 0) { // i.e. my_rank == 0
29 printf("%s", msg);
30 }
31}
32
33/*******************************************************************************
34 * \brief Returns the smaller of the two integers (missing from the C standard).
35 * \author Ole Schuett
36 ******************************************************************************/
37static inline int imin(int x, int y) { return (x < y ? x : y); }
38
39/*******************************************************************************
40 * \brief Private routine for creating a distribution.
41 * \author Ole Schuett
42 ******************************************************************************/
43static dbm_distribution_t *create_dist(const int nrows, const int ncols,
44 const cp_mpi_comm_t comm) {
45 int cart_dims[2], cart_periods[2], cart_coords[2];
46 cp_mpi_cart_get(comm, 2, cart_dims, cart_periods, cart_coords);
47
48 // Create distribution.
49 assert(0 < nrows && 0 < ncols);
50 int *row_dist = malloc(nrows * sizeof(int));
51 int *col_dist = malloc(ncols * sizeof(int));
52 assert(row_dist != NULL && col_dist != NULL);
53 for (int i = 0; i < nrows; i++) {
54 row_dist[i] = i % cart_dims[0];
55 }
56 for (int i = 0; i < ncols; i++) {
57 col_dist[i] = i % cart_dims[1];
58 }
59 const int fortran_comm = cp_mpi_comm_c2f(comm);
60 dbm_distribution_t *dist = NULL;
61 dbm_distribution_new(&dist, fortran_comm, nrows, ncols, row_dist, col_dist);
62 free(row_dist);
63 free(col_dist);
64 return dist;
65}
66
67/*******************************************************************************
68 * \brief Private routine for drawing block sizes from [size_min, size_max].
69 * \author Ole Schuett
70 ******************************************************************************/
71static void set_some_sizes(const int nsizes, const int size_min,
72 const int size_max, int sizes[nsizes]) {
73 assert(0 < size_min && size_min <= size_max);
74 const int range = size_max - size_min + 1;
75 for (int i = 0; i < nsizes; i++) {
76 sizes[i] = (1 < range ? size_min + rand() % range : size_max);
77 }
78}
79
80/*******************************************************************************
81 * \brief Private routine for creating a distribution and an empty matrix.
82 * \author Ole Schuett
83 ******************************************************************************/
84static dbm_matrix_t *
85create_some_matrix(const int nrows, const int ncols, const int nrows_min,
86 const int nrows_max, const int ncols_min,
87 const int ncols_max, const cp_mpi_comm_t comm) {
88 // Create distribution.
89 dbm_distribution_t *dist = create_dist(nrows, ncols, comm);
90
91 // Create matrix.
92 assert(0 < nrows && 0 < ncols);
93 int *row_sizes = malloc(nrows * sizeof(int));
94 int *col_sizes = malloc(ncols * sizeof(int));
95 assert(row_sizes != NULL && col_sizes != NULL);
96 set_some_sizes(nrows, nrows_min, nrows_max, row_sizes);
97 set_some_sizes(ncols, ncols_min, ncols_max, col_sizes);
98 dbm_matrix_t *matrix = NULL;
99 dbm_create(&matrix, dist, "some name", nrows, ncols, row_sizes, col_sizes);
101 free(row_sizes);
102 free(col_sizes);
103 return matrix;
104}
105
106/*******************************************************************************
107 * \brief Private routine for reserving all blocks of the given matrix.
108 * \author Ole Schuett
109 ******************************************************************************/
110static void reserve_all_blocks(dbm_matrix_t *matrix) {
111 int nrows, ncols;
112 const int *row_sizes, *col_sizes;
113 dbm_get_row_sizes(matrix, &nrows, &row_sizes);
114 dbm_get_col_sizes(matrix, &ncols, &col_sizes);
115
116#pragma omp parallel
117 {
118 int nblocks = 0;
119#pragma omp for collapse(2)
120 for (int row = 0; row < nrows; row++) {
121 for (int col = 0; col < ncols; col++) {
122 if (dbm_get_stored_coordinates(matrix, row, col) ==
123 matrix->dist->my_rank) {
124 ++nblocks;
125 }
126 }
127 }
128 int *reserve_row = NULL, *reserve_col = NULL;
129 if (0 < nblocks) {
130 reserve_row = malloc(nblocks * sizeof(int));
131 reserve_col = malloc(nblocks * sizeof(int));
132 assert(reserve_row != NULL && reserve_col != NULL);
133 }
134 int iblock = 0;
135#pragma omp for collapse(2)
136 for (int row = 0; row < nrows; row++) {
137 for (int col = 0; col < ncols; col++) {
138 if (dbm_get_stored_coordinates(matrix, row, col) ==
139 matrix->dist->my_rank) {
140 reserve_row[iblock] = row;
141 reserve_col[iblock] = col;
142 ++iblock;
143 }
144 }
145 }
146 assert(iblock == nblocks);
147 dbm_reserve_blocks(matrix, nblocks, reserve_row, reserve_col);
148 free(reserve_row);
149 free(reserve_col);
150 }
151}
152
153/*******************************************************************************
154 * \brief Private routine for setting all blocks.
155 * \author Ole Schuett
156 ******************************************************************************/
157static void set_all_blocks(dbm_matrix_t *matrix) {
158#pragma omp parallel
159 {
160 dbm_iterator_t *iter = NULL;
161 dbm_iterator_start(&iter, matrix);
162 while (dbm_iterator_blocks_left(iter)) {
163 int row, col, row_size, col_size;
164 double *block;
165 dbm_iterator_next_block(iter, &row, &col, &block, &row_size, &col_size);
166 // Blocks of the same size differ, which exposes mixed up blocks.
167 const int shift = (31 * row + 17 * col) % 101;
168 const int block_size = row_size * col_size;
169 for (int i = 0; i < block_size; i++) {
170 block[i] = 1.0 / (i + 1 + shift);
171 }
172 }
173 dbm_iterator_stop(iter);
174 }
175}
176/*******************************************************************************
177 * \brief Run a benchmark of dbm_multiply with given block sizes. The blocks
178 * of A have 1 to m rows and those of B have 1 to n columns, unless
179 * DBM_MINIAPP_FIXED is set (non-zero), e.g., for homogeneous batches.
180 * \author Ole Schuett
181 ******************************************************************************/
182void benchmark_multiply(const int M, const int N, const int K, const int m,
183 const int n, const int k, const cp_mpi_comm_t comm) {
184 const char *const fixed_env = getenv("DBM_MINIAPP_FIXED");
185 const bool fixed = (NULL != fixed_env && 0 != atoi(fixed_env));
186
187 srand(25071975); // same matrices regardless of preceding benchmarks
188 dbm_matrix_t *matrix_a =
189 create_some_matrix(M, K, fixed ? m : 1, m, k, k, comm);
190 dbm_matrix_t *matrix_b =
191 create_some_matrix(K, N, k, k, fixed ? n : 1, n, comm);
192 dbm_distribution_t *dist_c = create_dist(M, N, comm);
193 dbm_matrix_t *matrix_c = NULL, *matrix_d = NULL;
194 dbm_create(&matrix_c, dist_c, "result", M, N, matrix_a->row_sizes,
195 matrix_b->col_sizes);
197
198 reserve_all_blocks(matrix_a);
199 reserve_all_blocks(matrix_b);
200 set_all_blocks(matrix_a);
201 set_all_blocks(matrix_b);
202
203 const char *const verify_env = getenv("DBM_MULTIPLY_VERIFY");
204 const int skip_verify = (NULL == verify_env ? 0 : (atoi(verify_env) + 1));
205
206 if (0 == skip_verify) {
207 dbm_distribution_t *const dist_shared = matrix_c->dist;
208 dbm_create(&matrix_d, dist_shared, matrix_c->name, matrix_c->nrows,
209 matrix_c->ncols, matrix_c->row_sizes, matrix_c->col_sizes);
210 dbm_copy(matrix_d, matrix_c);
211 }
212
213 int64_t flop = 0;
214 const double time_start_multiply = omp_get_wtime();
215 dbm_multiply(false, false, 1.0, matrix_a, matrix_b, 1.0, matrix_c, false,
216 1e-8, &flop);
217 const double time_end_multiply = omp_get_wtime();
218
219 if (cp_mpi_comm_rank(comm) == 0) {
220 printf("%5i x %5i x %5i with %3i x %3i x %3i blocks: ", M, N, K, m, n, k);
221 }
222
223 if (NULL != matrix_d) { // Calculate result on the host for validation.
224 dbm_multiply(false, false, 1.0, matrix_a, matrix_b, 1.0, matrix_d, false,
225 1e-8, NULL);
226
227 const double maxeps = 1E-5, epsilon = dbm_maxeps(matrix_d, matrix_c);
228 if (maxeps < epsilon) {
229 printf("ERROR\n");
230 fprintf(stderr, "Failed validation (epsilon=%f).\n", epsilon);
231 exit(1);
232 }
233 dbm_release(matrix_d);
234 }
235
236 cp_mpi_sum_int64(&flop, 1, comm);
237 if (cp_mpi_comm_rank(comm) == 0) {
238 const double duration = time_end_multiply - time_start_multiply;
239 printf("%6.3f s => %6.1f GFLOP/s\n", duration, 1e-9 * flop / duration);
240 fflush(stdout);
241 }
242
243 dbm_release(matrix_a);
244 dbm_release(matrix_b);
245 dbm_release(matrix_c);
246}
247
248/*******************************************************************************
249 * \brief Private routine for parsing a triplet like MxNxK, where omitted
250 * components default to the first one. Returns false if invalid.
251 * \author Hans Pabst
252 ******************************************************************************/
253static bool parse_triplet(const char *arg, int triplet[3]) {
254 const char *const delims = "x,;:|/";
255 int ntriplet = 0;
256 while (ntriplet < 3 && '\0' != *arg) {
257 char *end = NULL;
258 const long value = strtol(arg, &end, 10);
259 if (end == arg || value <= 0 || INT_MAX < value) {
260 break;
261 }
262 triplet[ntriplet++] = (int)value;
263 for (arg = end; '\0' != *arg && NULL != strchr(delims, *arg); ++arg) {
264 }
265 }
266 for (int i = ntriplet; 0 < ntriplet && i < 3; i++) {
267 triplet[i] = triplet[0];
268 }
269 return (0 < ntriplet && '\0' == *arg);
270}
271
272/*******************************************************************************
273 * \brief Private routine for running benchmarks given as pairs of triplets,
274 * i.e., block sizes followed by the matrix size in blocks (default:
275 * 128x128x128 if the last pair is incomplete).
276 * \author Hans Pabst
277 ******************************************************************************/
278static bool run_benchmarks(const int nargs, char *args[],
279 const cp_mpi_comm_t comm) {
280 bool valid = (0 < nargs);
281 for (int i = 0; valid && i < nargs; i += 2) {
282 int mnk[3], MNK[3] = {128, 128, 128};
283 valid = parse_triplet(args[i], mnk) &&
284 (nargs <= i + 1 || parse_triplet(args[i + 1], MNK));
285 if (valid) {
286 benchmark_multiply(MNK[0], MNK[1], MNK[2], mnk[0], mnk[1], mnk[2], comm);
287 }
288 }
289 return valid;
290}
291
292/*******************************************************************************
293 * \brief Stand-alone miniapp for smoke-testing and benchmarking dbm_multiply.
294 * \author Ole Schuett
295 ******************************************************************************/
296int main(int argc, char *argv[]) {
297 int result = EXIT_SUCCESS;
298
299 cp_mpi_init(&argc, &argv);
301
302 const cp_mpi_comm_t world_comm = cp_mpi_get_comm_world();
303 const int nranks = cp_mpi_comm_size(world_comm);
304 const int my_rank = cp_mpi_comm_rank(world_comm);
305
306 if (offload_get_device_count() > 0) {
308 }
309
310 // Create 2D cart.
311 int dims[2] = {0, 0};
312 cp_mpi_dims_create(nranks, 2, dims);
313 const int periods[2] = {true, true};
314 cp_mpi_comm_t comm = cp_mpi_cart_create(world_comm, 2, dims, periods, false);
315
316 if (my_rank == 0) {
317 printf("OpenMP-threads: %i GPUs: %i", omp_get_max_threads(),
318 imin(offload_get_device_count(), nranks));
319#if defined(__parallel)
320 printf(" MPI-ranks: %i MPI-cart: %i x %i", nranks, dims[0], dims[1]);
321#else
322 printf(" MPI: n/a");
323#endif
324 printf("\n\n");
325 fflush(stdout);
326 }
327
328 if (1 >= argc) {
329 benchmark_multiply(16384, 128, 128, 4, 4, 4, comm);
330 benchmark_multiply(128, 16384, 128, 4, 4, 4, comm);
331 benchmark_multiply(128, 128, 16384, 4, 4, 4, comm);
332 benchmark_multiply(645, 645, 645, 4, 4, 4, comm);
333 if (my_rank == 0)
334 printf("\n");
335
336 benchmark_multiply(60, 500, 500, 128, 4, 4, comm);
337 benchmark_multiply(500, 60, 500, 4, 128, 4, comm);
338 benchmark_multiply(500, 500, 60, 4, 4, 128, comm);
339 if (my_rank == 0)
340 printf("\n");
341
342 benchmark_multiply(500, 60, 60, 4, 128, 128, comm);
343 benchmark_multiply(60, 500, 60, 128, 4, 128, comm);
344 benchmark_multiply(60, 60, 500, 128, 128, 4, comm);
345 if (my_rank == 0)
346 printf("\n");
347
348 benchmark_multiply(350, 350, 350, 23, 23, 23, comm);
349 benchmark_multiply(250, 250, 250, 32, 32, 32, comm);
350 benchmark_multiply(60, 60, 60, 128, 128, 128, comm);
351 } else { // Pairs of triplets from the command line or per line of a file.
352 FILE *const file = fopen(argv[1], "r");
353 bool valid = true;
354 if (NULL != file) {
355 char buffer[1024];
356 while (valid && NULL != fgets(buffer, sizeof(buffer), file)) {
357 char *args[2] = {NULL, NULL};
358 int nargs = 0;
359 for (char *arg = strtok(buffer, " \t\r\n"); NULL != arg;
360 arg = strtok(NULL, " \t\r\n")) {
361 valid = (nargs < 2);
362 if (!valid) {
363 break;
364 }
365 args[nargs++] = arg;
366 }
367 if (valid && 0 < nargs) { // skip empty lines
368 valid = run_benchmarks(nargs, args, comm);
369 }
370 }
371 fclose(file);
372 } else {
373 valid = run_benchmarks(argc - 1, argv + 1, comm);
374 }
375 if (!valid) {
376 if (my_rank == 0) {
377 fprintf(stderr, "Usage: %s [file | mxnxk [MxNxK] ...]\n", argv[0]);
378 }
379 result = EXIT_FAILURE;
380 }
381 }
382
383 if (EXIT_SUCCESS == result) {
384 const int fortran_comm = cp_mpi_comm_c2f(comm);
385 dbm_library_print_stats(fortran_comm, &print_func, my_rank);
386 offload_mempool_stats_print(fortran_comm, &print_func, my_rank);
387 }
389 cp_mpi_comm_free(&comm);
391 return result;
392}
393
394// EOF
int cp_mpi_comm_size(const cp_mpi_comm_t comm)
Wrapper around MPI_Comm_size.
Definition cp_mpi.c:194
cp_mpi_comm_t cp_mpi_cart_create(const cp_mpi_comm_t comm_old, const int ndims, const int dims[], const int periods[], const int reorder)
Wrapper around MPI_Cart_create.
Definition cp_mpi.c:239
void cp_mpi_init(int *argc, char ***argv)
Initialize MPI with MPI_THREAD_MULTIPLE or attach to an active MPI.
Definition cp_mpi.c:78
int cp_mpi_comm_c2f(const cp_mpi_comm_t comm)
Wrapper around MPI_Comm_c2f.
Definition cp_mpi.c:164
cp_mpi_comm_t cp_mpi_get_comm_world(void)
Returns MPI_COMM_WORLD.
Definition cp_mpi.c:138
void cp_mpi_cart_get(const cp_mpi_comm_t comm, int maxdims, int dims[], int periods[], int coords[])
Wrapper around MPI_Cart_get.
Definition cp_mpi.c:261
void cp_mpi_sum_int64(int64_t *values, const int count, const cp_mpi_comm_t comm)
Wrapper around MPI_Allreduce for op MPI_SUM and datatype MPI_INT64_T.
Definition cp_mpi.c:460
void cp_mpi_comm_free(cp_mpi_comm_t *comm)
Wrapper around MPI_Comm_free.
Definition cp_mpi.c:313
void cp_mpi_dims_create(const int nnodes, const int ndims, int dims[])
Wrapper around MPI_Dims_create.
Definition cp_mpi.c:224
void cp_mpi_finalize(void)
Detach from MPI and finalize it only if cp_mpi_init initialized it.
Definition cp_mpi.c:112
int cp_mpi_comm_rank(const cp_mpi_comm_t comm)
Wrapper around MPI_Comm_rank.
Definition cp_mpi.c:177
int cp_mpi_comm_t
Definition cp_mpi.h:18
void dbm_distribution_new(dbm_distribution_t **dist_out, const int fortran_comm, const int nrows, const int ncols, const int row_dist[nrows], const int col_dist[ncols])
Creates a new two dimensional distribution.
void dbm_distribution_release(dbm_distribution_t *dist)
Decreases the reference counter of the given distribution.
void dbm_library_finalize(void)
Finalizes the DBM library.
Definition dbm_library.c:70
void dbm_library_print_stats(const int fortran_comm, void(*print_func)(const char *, int, int), const int output_unit)
Prints statistics gathered by the DBM library.
void dbm_library_init(void)
Initializes the DBM library.
Definition dbm_library.c:36
void dbm_reserve_blocks(dbm_matrix_t *matrix, const int nblocks, const int rows[], const int cols[])
Adds list of blocks efficiently. The blocks will be filled with zeros. This routine must always be ca...
Definition dbm_matrix.c:335
bool dbm_iterator_blocks_left(const dbm_iterator_t *iter)
Tests whether the given iterator has any block left.
Definition dbm_matrix.c:522
void dbm_iterator_stop(dbm_iterator_t *iter)
Releases the given iterator.
Definition dbm_matrix.c:560
void dbm_get_col_sizes(const dbm_matrix_t *matrix, int *ncols, const int **col_sizes)
Returns the column block sizes of the given matrix.
Definition dbm_matrix.c:716
void dbm_create(dbm_matrix_t **matrix_out, dbm_distribution_t *dist, const char name[], const int nrows, const int ncols, const int row_sizes[nrows], const int col_sizes[ncols])
Creates a new matrix.
Definition dbm_matrix.c:22
void dbm_iterator_start(dbm_iterator_t **iter_out, const dbm_matrix_t *matrix)
Creates an iterator for the blocks of the given matrix. The iteration order is not stable....
Definition dbm_matrix.c:488
void dbm_iterator_next_block(dbm_iterator_t *iter, int *row, int *col, double **block, int *row_size, int *col_size)
Returns the next block from the given iterator.
Definition dbm_matrix.c:530
void dbm_get_row_sizes(const dbm_matrix_t *matrix, int *nrows, const int **row_sizes)
Returns the row block sizes of the given matrix.
Definition dbm_matrix.c:706
void dbm_copy(dbm_matrix_t *matrix_a, const dbm_matrix_t *matrix_b)
Copies content of matrix_b into matrix_a. Matrices must have the same row/col block sizes and distrib...
Definition dbm_matrix.c:90
double dbm_maxeps(const dbm_matrix_t *matrix_a, const dbm_matrix_t *matrix_b)
Calculates maximum relative difference between matrix_a and matrix_b.
Definition dbm_matrix.c:614
int dbm_get_stored_coordinates(const dbm_matrix_t *matrix, const int row, const int col)
Returns the MPI rank on which the given block should be stored.
Definition dbm_matrix.c:746
void dbm_release(dbm_matrix_t *matrix)
Releases a matrix and all its ressources.
Definition dbm_matrix.c:72
static void print_func(const char *msg, int msglen, int output_unit)
Wrapper for printf, passed to dbm_library_print_stats.
Definition dbm_miniapp.c:26
static void set_some_sizes(const int nsizes, const int size_min, const int size_max, int sizes[nsizes])
Private routine for drawing block sizes from [size_min, size_max].
Definition dbm_miniapp.c:71
static bool run_benchmarks(const int nargs, char *args[], const cp_mpi_comm_t comm)
Private routine for running benchmarks given as pairs of triplets, i.e., block sizes followed by the ...
static dbm_distribution_t * create_dist(const int nrows, const int ncols, const cp_mpi_comm_t comm)
Private routine for creating a distribution.
Definition dbm_miniapp.c:43
static int imin(int x, int y)
Returns the smaller of the two integers (missing from the C standard).
Definition dbm_miniapp.c:37
void benchmark_multiply(const int M, const int N, const int K, const int m, const int n, const int k, const cp_mpi_comm_t comm)
Run a benchmark of dbm_multiply with given block sizes. The blocks of A have 1 to m rows and those of...
static bool parse_triplet(const char *arg, int triplet[3])
Private routine for parsing a triplet like MxNxK, where omitted components default to the first one....
static void set_all_blocks(dbm_matrix_t *matrix)
Private routine for setting all blocks.
static dbm_matrix_t * create_some_matrix(const int nrows, const int ncols, const int nrows_min, const int nrows_max, const int ncols_min, const int ncols_max, const cp_mpi_comm_t comm)
Private routine for creating a distribution and an empty matrix.
Definition dbm_miniapp.c:85
static void reserve_all_blocks(dbm_matrix_t *matrix)
Private routine for reserving all blocks of the given matrix.
void dbm_multiply(const bool transa, const bool transb, const double alpha, const dbm_matrix_t *matrix_a, const dbm_matrix_t *matrix_b, const double beta, dbm_matrix_t *matrix_c, const bool retain_sparsity, const double filter_eps, int64_t *flop)
Performs a multiplication of two dbm_matrix_t matrices. See dbm_matrix.h for details.
static void const int const int i
int main()
Unit test of the C-interface provided via libcp2k.h.
int offload_get_device_count(void)
Returns the number of available devices.
void offload_set_chosen_device(int device_id)
Selects the chosen device to be used.
void offload_mempool_stats_print(int fortran_comm, void(*print_func)(const char *, int, int), int output_unit)
Print allocation statistics..
Internal struct for storing a two dimensional distribution.
Internal struct for storing a block iterator.
Definition dbm_matrix.h:34
Internal struct for storing a matrix.
Definition dbm_matrix.h:19
int * row_sizes
Definition dbm_matrix.h:24
char * name
Definition dbm_matrix.h:21
int * col_sizes
Definition dbm_matrix.h:25
dbm_distribution_t * dist
Definition dbm_matrix.h:20