Shamrock 2025.10.0
Astrophysical Code
Loading...
Searching...
No Matches
MicroBenchmark.cpp
Go to the documentation of this file.
1// -------------------------------------------------------//
2//
3// SHAMROCK code for hydrodynamics
4// Copyright (c) 2021-2026 Timothée David--Cléris <tim.shamrock@proton.me>
5// SPDX-License-Identifier: CeCILL Free Software License Agreement v2.1
6// Shamrock is licensed under the CeCILL 2.1 License, see LICENSE for more information
7//
8// -------------------------------------------------------//
9
15
18#include "shambase/string.hpp"
19#include "shambase/time.hpp"
28#include "shambackends/math.hpp"
29#include "shamcomm/wrapper.hpp"
34#include <stdexcept>
35#include <vector>
36
37namespace {
38
39 std::unordered_map<std::string, double> microbench_results = {};
40
41}
42
43namespace shamsys::microbench {
45 void p2p_bandwidth(u32 wr_sender, u32 wr_receiv);
46
48 void p2p_latency(u32 wr1, u32 wr2);
49
51 template<typename T>
52 void saxpy();
53
55 template<typename T>
57
59 template<typename T, sham::benchmarks::IntChainOp op>
61
63 void vector_allgather(u32 el_per_rank);
64
65} // namespace shamsys::microbench
66
108
109void shamsys::microbench::p2p_bandwidth(u32 wr_sender, u32 wr_receiv) {
110 StackEntry stack_loc{};
111
113
114 u64 length = 1024UL * 1014UL * 8UL; // 8MB messages
115 shamcomm::CommunicationBuffer buf_recv{length, instance::get_compute_scheduler_ptr()};
116 shamcomm::CommunicationBuffer buf_send{length, instance::get_compute_scheduler_ptr()};
117
118 std::vector<MPI_Request> rqs;
119
120 f64 t = 0;
121 u64 loops = 0;
122 bool is_used = false;
123 do {
124 loops++;
125
126 mpi::barrier(MPI_COMM_WORLD);
127 f64 t_start = MPI_Wtime();
128
129 if (wr == wr_sender) {
130 rqs.push_back(MPI_Request{});
131 u32 rq_index = rqs.size() - 1;
132 auto &rq = rqs[rq_index];
134 buf_send.get_ptr(), length, MPI_BYTE, wr_receiv, 0, MPI_COMM_WORLD, &rq);
135 is_used = true;
136 }
137
138 if (wr == wr_receiv) {
139 MPI_Status s;
141 buf_recv.get_ptr(), length, MPI_BYTE, wr_sender, 0, MPI_COMM_WORLD, &s);
142 is_used = true;
143 }
144
145 if (!is_used) {
146 t = 1;
147 }
148 std::vector<MPI_Status> st_lst(rqs.size());
149 if (rqs.size() > 0) {
150 shamcomm::mpi::Waitall(rqs.size(), rqs.data(), st_lst.data());
151 }
152 f64 t_end = MPI_Wtime();
153 t += t_end - t_start;
154
155 } while (shamalgs::collective::allreduce_min(t) < 1);
156
157 f64 bw = f64(length * loops) / t;
158
159 microbench_results["p2p_bandwidth"] = bw;
160
161 if (shamcomm::world_rank() == 0) {
162 auto hr_bw = sham::to_human_readable<false>(bw);
164 sham::format(
165 " - p2p bandwidth : {:.2f} {}B.s^-1 (ranks : {} -> {}) (loops : {})",
166 hr_bw.value,
167 hr_bw.prefix,
168 wr_sender,
169 wr_receiv,
170 loops));
171 }
172}
173
175 StackEntry stack_loc{};
176
177 if (wr1 == wr2) {
179 "can not launch this test with same ranks");
180 }
181
183
184 u64 length = 8ULL; // 8B messages
185 shamcomm::CommunicationBuffer buf_recv{length, instance::get_compute_scheduler_ptr()};
186 shamcomm::CommunicationBuffer buf_send{length, instance::get_compute_scheduler_ptr()};
187
188 shambase::Timer bench_timer;
189 bench_timer.start();
190
191 f64 t = 0;
192 u64 loops = 0;
193 bool is_used = false;
194 do {
195 loops++;
196
197 mpi::barrier(MPI_COMM_WORLD);
198 f64 t_start = MPI_Wtime();
199
200 if (wr == wr1) {
201 MPI_Status s;
202 shamcomm::mpi::Send(buf_send.get_ptr(), length, MPI_BYTE, wr2, 0, MPI_COMM_WORLD);
203 shamcomm::mpi::Recv(buf_recv.get_ptr(), length, MPI_BYTE, wr2, 1, MPI_COMM_WORLD, &s);
204 is_used = true;
205 }
206
207 if (wr == wr2) {
208 MPI_Status s;
209 shamcomm::mpi::Recv(buf_recv.get_ptr(), length, MPI_BYTE, wr1, 0, MPI_COMM_WORLD, &s);
210 shamcomm::mpi::Send(buf_send.get_ptr(), length, MPI_BYTE, wr1, 1, MPI_COMM_WORLD);
211 is_used = true;
212 }
213
214 if (!is_used) {
215 t = 1;
216 }
217 f64 t_end = MPI_Wtime();
218 t += t_end - t_start;
219
220 bench_timer.stop();
221
222 } while (shamalgs::collective::allreduce_min(bench_timer.elapsed_sec()) < 1);
223
224 f64 latency = t / f64(loops);
225 microbench_results["p2p_latency"] = latency;
226
227 if (shamcomm::world_rank() == 0) {
229 sham::format(
230 " - p2p latency : {:.4e} s (ranks : {} <-> {}) (loops : {})",
231 latency,
232 wr1,
233 wr2,
234 loops));
235 }
236}
237
238template<typename T>
240 int Tsize = sizeof(T);
241
242 std::string type_name;
243 T init_x, init_y, a;
244 if constexpr (std::is_same_v<T, f32>) {
245 type_name = "f32";
246 init_x = 1.0f;
247 init_y = 2.0f;
248 a = 2.0f;
249 } else if constexpr (std::is_same_v<T, f64>) {
250 type_name = "f64";
251 init_x = 1.0;
252 init_y = 2.0;
253 a = 2.0;
254 } else if constexpr (std::is_same_v<T, f32_2>) {
255 type_name = "f32_2";
256 init_x = {1.0f, 1.0f};
257 init_y = {2.0f, 2.0f};
258 a = {2.0f, 2.0f};
259 } else if constexpr (std::is_same_v<T, f64_2>) {
260 type_name = "f64_2";
261 init_x = {1.0, 1.0};
262 init_y = {2.0, 2.0};
263 a = {2.0, 2.0};
264 } else if constexpr (std::is_same_v<T, f32_3>) {
265 type_name = "f32_3";
266 init_x = {1.0f, 1.0f, 1.0f};
267 init_y = {2.0f, 2.0f, 2.0f};
268 a = {2.0f, 2.0f, 2.0f};
269 } else if constexpr (std::is_same_v<T, f64_3>) {
270 type_name = "f64_3";
271 init_x = {1.0, 1.0, 1.0};
272 init_y = {2.0, 2.0, 2.0};
273 a = {2.0, 2.0, 2.0};
274 } else if constexpr (std::is_same_v<T, f32_4>) {
275 type_name = "f32_4";
276 init_x = {1.0f, 1.0f, 1.0f, 1.0f};
277 init_y = {2.0f, 2.0f, 2.0f, 2.0f};
278 a = {2.0f, 2.0f, 2.0f, 2.0f};
279 } else if constexpr (std::is_same_v<T, f64_4>) {
280 type_name = "f64_4";
281 init_x = {1.0, 1.0, 1.0, 1.0};
282 init_y = {2.0, 2.0, 2.0, 2.0};
283 a = {2.0, 2.0, 2.0, 2.0};
284 } else {
286 }
287
288 auto bench_step = [&](int N) {
290 instance::get_compute_scheduler_ptr(), N, init_x, init_y, a, Tsize, N < (1 << 17));
291 };
292
293 auto benchmark = [&]() {
294 size_t N = (1 << 15);
295
296 auto &dev_ctx = shambase::get_check_ref(instance::get_compute_scheduler().ctx);
297 auto &dev_ptr = dev_ctx.device;
298 auto &dev = shambase::get_check_ref(dev_ptr);
299
300 size_t max_alloc
301 = std::min<size_t>(dev.prop.max_mem_alloc_size_dev, dev.prop.global_mem_size);
302 double max_size = double(max_alloc) / (Tsize * 4); // there is 2 allocations so /4
303 if (max_size >= (1 << 30)) {
304 max_size = (1 << 30);
305 }
306
307 auto result = bench_step(shambase::narrow_or_throw<i32>(N));
308
309 for (; N <= (1 << 30) && static_cast<double>(N) <= max_size; N *= 2) {
310 result = bench_step(shambase::narrow_or_throw<i32>(N));
311
312 // std::cout << N << " " << result_new.seconds << " " << result_new.bandwidth
313 // << std::endl;
314
315 if (result.seconds > 1e-3) {
316 break;
317 }
318 }
319
320 return result;
321 };
322
323 auto result = benchmark();
324
325 f64 bw = result.bandwidth * 1e9;
326
327 f64 min_bw = shamalgs::collective::allreduce_min(bw);
328 f64 max_bw = shamalgs::collective::allreduce_max(bw);
329 f64 sum_bw = shamalgs::collective::allreduce_sum(bw);
330 f64 avg_bw = sum_bw / (f64) shamcomm::world_size();
331
332 microbench_results["saxpy_" + type_name] = sum_bw;
333
334 if (shamcomm::world_rank() == 0) {
335 auto hr_bw = sham::to_human_readable<false>(sum_bw);
337 sham::format(
338 " - saxpy ({}) : {:.2f} {}B.s^-1 (min = {:.1e}, max = {:.1e}, avg = {:.1e}) "
339 "({:.1e} ms, {})",
340 type_name,
341 hr_bw.value,
342 hr_bw.prefix,
343 min_bw,
344 max_bw,
345 avg_bw,
346 result.seconds * 1e3,
347 shambase::readable_sizeof(result.byte_used)));
348 }
349}
350
351template<typename T>
353 int N = (1 << 22);
354
355 auto result
356 = sham::benchmarks::fma_chains_bench<T>(instance::get_compute_scheduler_ptr(), N, 0.2);
357
358 std::string type_name;
359 f64 flops_multiplier = 1;
360 if constexpr (std::is_same_v<T, f32>) {
361 type_name = "f32";
362 flops_multiplier = 1;
363 } else if constexpr (std::is_same_v<T, f64>) {
364 type_name = "f64";
365 flops_multiplier = 1;
366 } else if constexpr (std::is_same_v<T, f32_2>) {
367 type_name = "f32_2";
368 flops_multiplier = 2;
369 } else if constexpr (std::is_same_v<T, f64_2>) {
370 type_name = "f64_2";
371 flops_multiplier = 2;
372 } else if constexpr (std::is_same_v<T, f32_3>) {
373 type_name = "f32_3";
374 flops_multiplier = 3;
375 } else if constexpr (std::is_same_v<T, f64_3>) {
376 type_name = "f64_3";
377 flops_multiplier = 3;
378 } else if constexpr (std::is_same_v<T, f32_4>) {
379 type_name = "f32_4";
380 flops_multiplier = 4;
381 } else if constexpr (std::is_same_v<T, f64_4>) {
382 type_name = "f64_4";
383 flops_multiplier = 4;
384 } else {
386 }
387
388 f64 min_flop = shamalgs::collective::allreduce_min(result.flops);
389 f64 max_flop = shamalgs::collective::allreduce_max(result.flops);
390 f64 sum_flop = shamalgs::collective::allreduce_sum(result.flops);
391 f64 avg_flop = sum_flop / (f64) shamcomm::world_size();
392
393 microbench_results["fma_chains_" + type_name] = sum_flop * flops_multiplier;
394
395 if (shamcomm::world_rank() == 0) {
396 auto hr_flop = sham::to_human_readable<false>(sum_flop * flops_multiplier);
398 sham::format(
399 " - fma_chains ({}) : {:.2f} {}flops (min = {:.1e}, max = {:.1e}, avg = {:.1e}) "
400 "({:.1e} ms, rotations = {})",
401 type_name,
402 hr_flop.value,
403 hr_flop.prefix,
404 min_flop * flops_multiplier,
405 max_flop * flops_multiplier,
406 avg_flop * flops_multiplier,
407 result.seconds * 1e3,
408 result.nrotations));
409 }
410}
411
412template<typename T, sham::benchmarks::IntChainOp op>
414 int N = (1 << 22);
415
416 auto result
417 = sham::benchmarks::int_chains_bench<T, op>(instance::get_compute_scheduler_ptr(), N, 0.2);
418
419 std::string type_name;
420 if constexpr (std::is_same_v<T, u32>) {
421 type_name = "u32";
422 } else if constexpr (std::is_same_v<T, u64>) {
423 type_name = "u64";
424 } else {
426 }
427
428 std::string op_name = (op == sham::benchmarks::IntChainOp::Mul) ? "mul" : "add";
429
430 f64 min_iops = shamalgs::collective::allreduce_min(result.iops);
431 f64 max_iops = shamalgs::collective::allreduce_max(result.iops);
432 f64 sum_iops = shamalgs::collective::allreduce_sum(result.iops);
433 f64 avg_iops = sum_iops / (f64) shamcomm::world_size();
434
435 microbench_results["int_" + op_name + "_chains_" + type_name] = sum_iops;
436
437 if (shamcomm::world_rank() == 0) {
438 auto hr_iops = sham::to_human_readable<false>(sum_iops);
440 sham::format(
441 " - int_{}_chains ({}) : {:.2f} {}iops (min = {:.1e}, max = {:.1e}, avg = {:.1e}) "
442 "({:.1e} ms, rotations = {})",
443 op_name,
444 type_name,
445 hr_iops.value,
446 hr_iops.prefix,
447 min_iops,
448 max_iops,
449 avg_iops,
450 result.seconds * 1e3,
451 result.nrotations));
452 }
453}
454
456
457 using T = u64;
458 std::vector<u64> send_data(el_per_rank);
459
460 std::vector<u64> recv_data;
461
462 f64 t = 0;
463 u64 loops = 0;
464
465 auto benchmark_step = [&]() {
466 shamcomm::mpi::Barrier(MPI_COMM_WORLD);
467 f64 t_start = MPI_Wtime();
468 shamalgs::collective::vector_allgatherv(send_data, recv_data, MPI_COMM_WORLD);
469 f64 t_end = MPI_Wtime();
470 t += t_end - t_start;
471 loops++;
472 };
473
474 do {
475 benchmark_step();
476 } while (shamalgs::collective::allreduce_min(t) < 0.1);
477
478 t /= loops;
479
480 f64 min_t = shamalgs::collective::allreduce_min(t);
481 f64 max_t = shamalgs::collective::allreduce_max(t);
482 f64 sum_t = shamalgs::collective::allreduce_sum(t);
483 f64 avg_t = sum_t / (f64) shamcomm::world_size();
484
485 microbench_results["vector_allgather_u64_" + std::to_string(el_per_rank)] = avg_t;
486
487 if (shamcomm::world_rank() == 0) {
489 sham::format(
490 " - vector_allgather (u64, n={:4}) : {:.3e} s (min = {:.2e}, max = {:.2e}, loops = "
491 "{})",
492 el_per_rank,
493 avg_t,
494 min_t,
495 max_t,
496 loops));
497 }
498}
499
500const std::unordered_map<std::string, double> &shamsys::get_microbench_results(bool allow_run) {
501 if (allow_run && microbench_results.empty()) {
503 }
504 return microbench_results;
505}
Shamrock communication buffers.
void vector_allgather(u32 el_per_rank)
Vector allgather benchmark.
void p2p_latency(u32 wr1, u32 wr2)
MPI point-to-point latency benchmark.
void p2p_bandwidth(u32 wr_sender, u32 wr_receiv)
MPI point-to-point bandwidth benchmark.
void int_chains_rotation()
Integer chains benchmark to get the maximum integer performance.
void saxpy()
SAXPY benchmark, to get the maximum bandwidth.
void fma_chains_rotation()
FMA chains benchmark to get the maximum floating point performance.
This header does the MPI include and wrap MPI calls.
Header file describing a Node Instance.
double f64
Alias for double.
std::uint32_t u32
32 bit unsigned integer
std::uint64_t u64
64 bit unsigned integer
Class Timer measures the time elapsed since the timer was started.
Definition Timer.hpp:36
f64 elapsed_sec() const
Converts the stored nanosecond time to a floating point representation in seconds.
Definition Timer.hpp:88
void start()
Starts the timer.
Definition Timer.hpp:51
void stop()
Stops the timer and stores the elapsed time in nanoseconds.
Definition Timer.hpp:65
Shamrock communication buffers.
This header file contains utility functions related to exception handling in the code.
std::vector< int > vector_allgatherv(const std::vector< T > &send_vec, const MPI_Datatype &send_type, std::vector< T > &recv_vec, const MPI_Datatype &recv_type, const MPI_Comm comm)
allgatherv on vector with size query (size querying variant of vector_allgatherv_ks) //TODO add fault...
Definition exchanges.hpp:98
Port of Argonne National Laboratory's FMA chains benchmark flops.cpp.
fma_chains_result fma_chains_bench(DeviceScheduler_ptr sched, int N, f64 time_threshold)
Run the fma_chains benchmark.
Convert raw numeric values to human-readable SI-formatted pairs.
Integer ALU throughput benchmark (multiply chains vs add chains).
int_chains_result int_chains_bench(DeviceScheduler_ptr sched, int N, f64 time_threshold)
Run the int_chains benchmark.
@ Mul
multiply-add chains, 16 multiplies + 16 adds per rotation
human_readable_t to_human_readable(double value)
Convert a raw value to a human-readable scaled form with an SI prefix.
std::string readable_sizeof(double size)
given a sizeof value return a readble string Example : readable_sizeof(1e9) -> "1....
Definition string.hpp:80
T & get_check_ref(const std::unique_ptr< T > &ptr, SourceLocation loc=SourceLocation())
Takes a std::unique_ptr and returns a reference to the object it holds. It throws a std::runtime_erro...
Definition memory.hpp:112
ExcptTypes make_except_with_loc(std::string message, SourceLocation loc=SourceLocation{})
Create an exception with a message and a location.
i32 world_rank()
Gives the rank of the current process in the MPI communicator.
Definition worldInfo.cpp:41
i32 world_size()
Gives the size of the MPI communicator.
Definition worldInfo.cpp:39
void run_micro_benchmark()
Run latency & bandwidth benchmark those benchmark where adapted from osu_microbenchmark.
const std::unordered_map< std::string, double > & get_microbench_results(bool allow_run=false)
Get the microbench results.
saxpy_result saxpy_bench(DeviceScheduler_ptr sched, int N, T init_x, T init_y, T a, int load_size, bool check_correctness)
saxpy function for benchmarking.
Definition saxpy.hpp:70
void raw_ln(Types... var2)
Prints a log message with multiple arguments followed by a newline.
Definition logs.hpp:89
This file contains the definition for the stacktrace related functionality.
shambase::details::BasicStackEntry StackEntry
Alias for shambase::details::BasicStackEntry.
void Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)
MPI wrapper for MPI_Recv.
Definition wrapper.cpp:210
void Barrier(MPI_Comm comm)
MPI wrapper for MPI_Barrier.
Definition wrapper.cpp:196
void Waitall(int count, MPI_Request array_of_requests[], MPI_Status *array_of_statuses)
MPI wrapper for MPI_Waitall.
Definition wrapper.cpp:189
void Send(const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
MPI wrapper for MPI_Send.
Definition wrapper.cpp:231
void Isend(const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request)
MPI wrapper for MPI_Isend.
Definition wrapper.cpp:87