Peano
Loading...
Searching...
No Matches
KernelMicrobenchmarkMain.py
Go to the documentation of this file.
1# This file is part of the ExaHyPE2 project. For conditions of distribution and
2# use, please see the copyright notice at www.peano-framework.org
3import peano4.api
4
5from ..Project import Project
6
7
9 """!
10
11 Optional realisation of exahype2.api.Project.mainfile_generator
12
13 It constructs a main function that times the performance of different functions
14 and assesses their accuracy.
15
16 """
17
18 def __init__(self, project: Project, function_calls: str, additional_includes: str):
19 super().__init__(project)
20 self.function_calls = function_calls
21 self.additional_includes = additional_includes
22
23 def construct_output(self, peano_project):
25 "Create main file for {}".format(self.project._project_name)
26 )
27 peano_project.main = _ExaHyPEKernelBenchmarkMainFileSequence(peano_project, self.project, self.function_calls, self.additional_includes)
28
29
31 """!
32
33 Adapter that speaks the construct_output(output, main_name) protocol
34 peano4.api.Project.generate() expects from its main attribute (see
35 peano4.api.runner.DefaultSequence, whose job this class takes over for
36 an ExaHyPE 2 project), but fills the header/implementation pair with
37 ExaHyPE-flavoured content instead of the mesh-agnostic default.
38
39 project is the peano4-level project (peano4.api.Project) this sequence
40 is attached to as .main - it is where SUBDIRECTORIES/SUBNAMESPACE/
41 rootnamespace come from, mirroring peano4.api.runner.DefaultSequence.
42
43 exahype_project is the exahype2.api.Project this main file is generated
44 for - it is where the runtime settings (TIMEOUT, NUMBER_OF_THREADS,
45 GPUS, MULTICORE_ORCHESTRATION) come from, as those live on the ExaHyPE
46 2 project rather than on the plain peano4 one.
47
48 """
49
50 HEADER_TEMPLATE = """
51// This file is part of the ExaHyPE2 project. For conditions of distribution and
52// use, please see the copyright notice at www.peano-framework.org
53#pragma once
54
55int main(int argc, char** argv);
56"""
57
58 CPP_TEMPLATE = """
59// This file is part of the ExaHyPE2 project. For conditions of distribution and
60// use, please see the copyright notice at www.peano-framework.org
61#include "{{CLASSNAME}}.h"
62
63#include "peano4/peano.h"
64#include "exahype2/Simulation.h"
65#include "exahype2/CellData.h"
66#include "tarch/NonCriticalAssertions.h"
67#include "tarch/accelerator/accelerator.h"
68#include "tarch/accelerator/Device.h"
69#include "tarch/multicore/multicore.h"
70#include "tarch/multicore/Core.h"
71#include "tarch/multicore/BooleanSemaphore.h"
72#include "tarch/multicore/Lock.h"
73#include "tarch/logging/Log.h"
74#include "tarch/logging/LogFilterFileReader.h"
75#include "tarch/timing/Measurement.h"
76#include "tarch/timing/Watch.h"
77{{ADDITIONAL_INCLUDES}}
78
79{% if SUBDIRECTORIES -%}
80{% for subdirectory in SUBDIRECTORIES %}
81#include "{{subdirectory}}Constants.h"
82#include "{{subdirectory}}repositories/DataRepository.h"
83#include "{{subdirectory}}repositories/StepRepository.h"
84#include "{{subdirectory}}repositories/SolverRepository.h"
85#include "{{subdirectory}}runners/SetupSimulation.h"
86#include "{{subdirectory}}runners/TimeStep.h"
87#include "{{subdirectory}}runners/Plot.h"
88#include "{{subdirectory}}runners/MeshAdaptation.h"
89{% endfor -%}
90{% else %}
91#include "Constants.h"
92#include "repositories/DataRepository.h"
93#include "repositories/StepRepository.h"
94#include "repositories/SolverRepository.h"
95#include "runners/SetupSimulation.h"
96#include "runners/TimeStep.h"
97#include "runners/Plot.h"
98#include "runners/MeshAdaptation.h"
99{%- endif %}
100
101using namespace {{NAMESPACE | join("::")}};
102tarch::logging::Log _log( "{{NAMESPACE | join("::")}}" );
103::tarch::timing::Measurement timingComputeKernel;
104::tarch::multicore::BooleanSemaphore validateOutcomeSemaphore;
105
106double validMaxEigenvalue = 0.0;
107double* validOutcome = nullptr;
108
109constexpr double TimeStamp = 0.5;
110constexpr double TimeStepSize = 1e-6;
111constexpr double CellSize = 0.1;
112constexpr double CellOffset = 4.0;
113
114constexpr int NumberOfInputEntriesPerPatch
115 = (repositories::{{SOLVER_INSTANCE}}.PatchSize + 2 * repositories::{{SOLVER_INSTANCE}}.HaloSize)
116 * (repositories::{{SOLVER_INSTANCE}}.PatchSize + 2 * repositories::{{SOLVER_INSTANCE}}.HaloSize)
117#if Dimensions == 3
118 * (repositories::{{SOLVER_INSTANCE}}.PatchSize + 2 * repositories::{{SOLVER_INSTANCE}}.HaloSize)
119#endif
120 * (repositories::{{SOLVER_INSTANCE}}.NumberOfUnknowns + repositories::{{SOLVER_INSTANCE}}.NumberOfAuxiliaryVariables);
121
122constexpr int NumberOfOutputEntriesPerPatch
123 = (repositories::{{SOLVER_INSTANCE}}.PatchSize + 0)
124 * (repositories::{{SOLVER_INSTANCE}}.PatchSize + 0)
125#if Dimensions == 3
126 * (repositories::{{SOLVER_INSTANCE}}.PatchSize + 0)
127#endif
128 * (repositories::{{SOLVER_INSTANCE}}.NumberOfUnknowns + repositories::{{SOLVER_INSTANCE}}.NumberOfAuxiliaryVariables);
129
130constexpr int NumberOfFiniteVolumesPerPatch
131 = repositories::{{SOLVER_INSTANCE}}.PatchSize
132 * repositories::{{SOLVER_INSTANCE}}.PatchSize
133#if Dimensions == 3
134 * repositories::{{SOLVER_INSTANCE}}.PatchSize
135#endif
136 ;
137
138/**
139 * Set input data according to the initial conditions provided for the solver
140 */
141void initInputData(double* Q) {
142 parallelDfor( volume, repositories::{{SOLVER_INSTANCE}}.PatchSize + 2 * repositories::{{SOLVER_INSTANCE}}.HaloSize) {
143 int index = peano4::utils::dLinearised(volume,repositories::{{SOLVER_INSTANCE}}.PatchSize + 2 * repositories::{{SOLVER_INSTANCE}}.HaloSize) * (repositories::{{SOLVER_INSTANCE}}.NumberOfUnknowns + repositories::{{SOLVER_INSTANCE}}.NumberOfAuxiliaryVariables);
144 repositories::{{SOLVER_INSTANCE}}.initPointWisely(
145 ::exahype2::fv::getVolumeCentre( CellOffset + 0.5 * CellSize, CellSize, repositories::{{SOLVER_INSTANCE}}.PatchSize, volume),
146 Q + index
147 );
148 } endParallelDfor
149}
150
151/**
152 * Store outcome of one compute kernel
153 *
154 * Make a persistent snapshot of a solution and assume, from hereon, that
155 * this snapshot is the valid data. You can call this routine as often as
156 * you want. Only the very first call will trigger a snapshot.
157 */
158void storeOutcome(const double* Q, const double maxEigenvalue) {
159 ::tarch::multicore::Lock lock(validateOutcomeSemaphore);
160 if (validOutcome == nullptr) {
161 validOutcome = new double[NumberOfOutputEntriesPerPatch]{0.0};
162 std::memcpy(validOutcome, Q, sizeof(double) * NumberOfOutputEntriesPerPatch);
163 validMaxEigenvalue = maxEigenvalue;
164 logInfo("storeOutcome()", "Bookmarked reference solution");
165 }
166}
167
168
169/**
170 * Validate data against pre-stored simulation outcome
171 *
172 * Works if and only if storeOutcome has been performed before.
173 *
174 * @return Tuple of maximum difference and total number of errors.
175 */
176std::tuple<double, int> validateOutcome(
177 double* Q,
178 int patchIndex,
179 const double maxEigenvalue
180) {
181 ::tarch::multicore::Lock lock(validateOutcomeSemaphore);
182 int index = 0;
183 int errors = 0;
184 double maxDifference = 0.0;
185
186 for (int i = 0; i < NumberOfOutputEntriesPerPatch; i++) {
187 if (not ::tarch::la::equals(Q[i], validOutcome[i], Accuracy)) {
188 errors++;
189 std::cerr.precision(16);
190 logError(
191 "validateOutcome()",
192 std::fixed
193 << "Q[" << i << "]!=validOutcome[" << i << "]: " << Q[i]
194 << "!=" << validOutcome[i]
195 );
196 }
197 maxDifference = std::max(maxDifference, std::abs(Q[i] - validOutcome[i]));
198 index++;
199 }
200
201 if (not tarch::la::equals(maxEigenvalue, validMaxEigenvalue, Accuracy)) {
202 std::cerr.precision(16);
203 logError(
204 "validateOutcome()",
205 std::fixed
206 << " maxEigenvalue[" << patchIndex << "]!=validMaxEigenvalue["
207 << patchIndex << "]: " << maxEigenvalue << "!=" << validMaxEigenvalue
208 );
209 errors++;
210 }
211
212 return {maxDifference, errors};
213}
214
215
216void reportRuntime(
217 const std::string& kernelIdentificator,
218 const ::tarch::timing::Measurement& timingKernelLaunch,
219 int patches
220) {
221 std::stringstream ss;
222 ss << "\\n";
223 ss << kernelIdentificator << ":\\n\\t";
224
225 ss << timingComputeKernel.getValue() << " |\\n\\t";
226 ss
227 << (timingComputeKernel.getValue() / patches / NumberOfFiniteVolumesPerPatch
228 )
229 << " |\\n\\t";
230 ss << timingComputeKernel.toString() << " |\\n\\t";
231
232 ss << timingKernelLaunch.getValue() << " |\\n\\t";
233 ss
234 << (timingKernelLaunch.getValue() / patches / NumberOfFiniteVolumesPerPatch
235 );
236 ss << " |\\n\\t" << timingKernelLaunch.toString();
237
238 logInfo("reportRuntime()", ss.str());
239 std::cout.flush();
240}
241
242/**
243 * We want to use all kernels exactly the same way. However, the various
244 * kernels all have slightly different signatures. So we write small helper
245 * functions (wrappers) which map the generic test signature onto the
246 * specific kernel.
247 *
248 * To make this possible, all parameters which are not part of the generic
249 * interface, i.e., which are not patch data or the boolean, have to be
250 * mapped onto template arguments.
251 */
252template <void (*Kernel)(
253 exahype2::CellData<double, double>&,
254 const ::exahype2::fv::rusanov::FluxFunctor&,
255 const ::exahype2::fv::rusanov::MaxEigenvalueFunctor&,
256 tarch::timing::Measurement&
257)>
258void wrapHostKernel(int device, exahype2::CellData<double, double>& patchData) {
259 assertionEquals(device, tarch::accelerator::Device::HostDevice);
260 Kernel(
261 patchData,
262 [&](
263 const double* __restrict__ Q,
264 const tarch::la::Vector<Dimensions, double>& x,
265 int normal,
266 double* __restrict__ F
267 )->void {
268 repositories::instanceOfEulerFVSmooth.flux(Q, x, normal, F);
269 },
270 [&](
271 const double* __restrict__ Q,
272 const tarch::la::Vector<Dimensions, double>& x,
273 int normal,
274 double* __restrict__ lambdaMax
275 )->void {
276 repositories::instanceOfEulerFVSmooth.maxEigenvalue(Q, x, normal, lambdaMax);
277 },
278 timingComputeKernel
279 );
280}
281
282template <void (*Kernel)(exahype2::CellData<double, double>&, tarch::timing::Measurement&)>
283void wrapHostKernel(int device, exahype2::CellData<double, double>& patchData) {
284 assertionEquals(device, tarch::accelerator::Device::HostDevice);
285 Kernel(patchData, timingComputeKernel);
286}
287
288 template <void (*Kernel)(exahype2::CellData<double, double>&, tarch::timing::Measurement&, peano4::utils::LoopPlacement),
289 peano4::utils::LoopPlacement loopPlacement>
290void wrapHostKernel(int device, exahype2::CellData<double, double>& patchData) {
291 assertionEquals(device, tarch::accelerator::Device::HostDevice);
292 Kernel(patchData, timingComputeKernel, loopPlacement);
293}
294
295template <void (*Kernel)(int, exahype2::CopyCellDataGPU<double, double>&, tarch::timing::Measurement&)>
296void wrapDeviceKernel(int device, ::exahype2::CellData<double, double>& patchData) {
297 assertion(device != tarch::accelerator::Device::HostDevice);
298 ::exahype2::CopyCellDataGPU<double, double> gpuCellData(patchData, NumberOfInputEntriesPerPatch, NumberOfOutputEntriesPerPatch, device);
299 Kernel(device, gpuCellData, timingComputeKernel);
300 gpuCellData.copyBackAndDestroy();
301}
302
303/**
304 * Run the benchmark for one particular number of patches
305 *
306 * This operation benchmarks exclusively the host performance. This happens in
307 * two steps: We first assess the baseline performance, i.e., the serial kernel
308 * call, and then we look if the multithreaded stateless optimisation pays off
309 * and what speedup we obtain.
310 *
311 * These two steps may run on multiple threads to mimic the spacetree tasking
312 * and domain decomposition. In OpenMP for example, there is
313 * one parallel loop around the two steps which invokes the same benchmark per
314 * loop entry. However, only the first thread always will report its runtime.
315 * If you are not bandwidth-bound, the data you obtain should thus be
316 * independent of the number of threads used.
317 *
318 * @param numberOfPatches Number of patches to study
319 */
320void runBenchmarks(int numberOfPatches, int launchingThreads) {
321 ::peano4::grid::GridTraversalEvent event;
322 event.setX(CellOffset);
323 event.setH(CellSize);
324 ::peano4::datamanagement::CellMarker marker(event);
325
326 auto assessKernel =
327 [&](
328 std::function<void(int device, ::exahype2::CellData<double, double>& patchData)> kernel,
329 const std::string& markerName,
330 int launchingThreads,
331 int device,
332 int patches
333 ) -> void {
334 timingComputeKernel.erase();
335 ::tarch::timing::Measurement timingKernelLaunch;
336
337 // TODO: Does the number of samples change the outcome of the solution?
338 for (int j = 0; j < NumberOfSamples; j++) {
339 //parallelFor(launchingThread, launchingThreads) {
340 //for (int launchingThread=0; launchingThread < launchingThreads; launchingThread++){
341 ::exahype2::CellData<double, double> patchData(patches);
342 for (int i = 0; i < patches; i++) {
343 patchData.QIn[i] = ::tarch::allocateMemory<double>(
344 NumberOfInputEntriesPerPatch,
345 ::tarch::MemoryLocation::ManagedSharedAcceleratorDeviceMemory
346 );
347 patchData.t[i] = TimeStamp;
348 patchData.dt[i] = TimeStepSize;
349 patchData.QOut[i] = ::tarch::allocateMemory<double>(
350 NumberOfOutputEntriesPerPatch,
351 ::tarch::MemoryLocation::ManagedSharedAcceleratorDeviceMemory
352 );
353 patchData.cellCentre[i] = ::tarch::la::Vector<Dimensions, double>(
354 CellOffset + 0.5 * CellSize
355 );
356 patchData.cellSize[i] = ::tarch::la::Vector<Dimensions, double>(
357 CellSize
358 );
359 patchData.maxEigenvalue[i] = 0.0;
360 initInputData(patchData.QIn[i]);
361 std::memset(
362 patchData.QOut[i],
363 0.0,
364 NumberOfOutputEntriesPerPatch * sizeof(double)
365 );
366 }
367
368 ::tarch::timing::Watch
369 watchKernelLaunch("::runBenchmarks", "assessKernel(...)", false);
370 kernel(device, patchData);
371 watchKernelLaunch.stop();
372 timingKernelLaunch.setValue(watchKernelLaunch.getCalendarTime());
373
374 if constexpr (Accuracy > 0.0) {
375 int errors = 0;
376 double maxDifference = 0.0;
377 for (int i = 0; i < patches; i++) {
378 storeOutcome(patchData.QOut[i], patchData.maxEigenvalue[i]);
379 auto [maxDifferencePerPatch, errorsPerPatch] = validateOutcome(
380 patchData.QOut[i],
381 i,
382 patchData.maxEigenvalue[i]
383 );
384 errors += errorsPerPatch;
385 maxDifference = std::max(maxDifference, maxDifferencePerPatch);
386 }
387
388 if (errors > 0) {
389 logError(
390 "runBenchmarks()",
391 "Max difference of outcome from \\""
392 << markerName << "\\" for all patches is " << maxDifference
393 << " (admissible accuracy=" << Accuracy << ")"
394 << " for " << errors << " entries"
395 );
396 std::abort();
397 }
398 }
399
400 for (int i = 0; i < patches; i++) {
401 ::tarch::freeMemory(
402 patchData.QIn[i],
403 ::tarch::MemoryLocation::ManagedSharedAcceleratorDeviceMemory
404 );
405 ::tarch::freeMemory(
406 patchData.QOut[i],
407 ::tarch::MemoryLocation::ManagedSharedAcceleratorDeviceMemory
408 );
409 }
410 //}
411 //endParallelFor
412 }
413
414 if (::tarch::mpi::Rank::getInstance().getRank() == ::tarch::mpi::Rank::getGlobalMasterRank()) {
415 reportRuntime(markerName, timingKernelLaunch, patches);
416 }
417 };
418
419 // Kernel launches
420 const int device = 0;
421 const int rank = ::tarch::mpi::Rank::getInstance().getRank();
422 const int numberOfRanks = ::tarch::mpi::Rank::getInstance().getNumberOfRanks(
423 );
424 const int patchesPerProcess = numberOfPatches / numberOfRanks;
425 const int remainder = numberOfPatches % numberOfRanks;
426 const int startPatch = rank * patchesPerProcess;
427 const int endPatch = startPatch + patchesPerProcess
428 + (rank == numberOfRanks - 1 ? remainder : 0);
429 const int localPatches = endPatch - startPatch;
430
431 // Headers
432 std::stringstream ss;
433 ss << std::left;
434 ss << "\\n";
435 ss << "Kernel ID:\\n\\t";
436 ss << "Compute Kernel Time |\\n\\t";
437 ss << "Compute Kernel Time (Normalised) |\\n\\t";
438 ss << "Compute Kernel String |\\n\\t";
439 ss << "Kernel Launch Time |\\n\\t";
440 ss << "Kernel Launch Time (Normalised) |\\n\\t";
441 ss << "Kernel Launch String";
442
443 if (::tarch::mpi::Rank::getInstance().getRank() == ::tarch::mpi::Rank::getGlobalMasterRank()) {
444 logInfo("runBenchmarks()", "Number of patches per rank: " << localPatches);
445 logInfo("runBenchmarks()", ss.str());
446 }
447
448 std::string deviceString;
449#ifdef Parallel
450 std::vector<int> devices(numberOfRanks);
451 MPI_Gather(&device, 1, MPI_INT, devices.data(), 1, MPI_INT, 0, MPI_COMM_WORLD);
452 for (int i = 0; i < numberOfRanks; i++) {
453 deviceString += std::to_string(devices[i]);
454 if (i < numberOfRanks - 1) {
455 deviceString += ",";
456 }
457 }
458#else
459 deviceString = std::to_string(device);
460#endif
461
462{{FUNCTION_CALLS}}
463}
464
465int main(int argc, char** argv) {
466 constexpr int ExitCodeSuccess = 0;
467 constexpr int ExitCodeInconsistentResults = 1;
468 constexpr int ExitCodeInvalidArguments = 2;
469 constexpr int ExitCodeInvalidBuild = 3;
470
471 peano4::initParallelEnvironment(&argc, &argv);
472
473 // Do this early, so people can use logInfo/logWarning/... properly from
474 // here on.
475 {% if LOG_FILTER_FILE!="" %}
476 if ( not tarch::logging::LogFilterFileReader::parsePlainTextFile( "{{LOG_FILTER_FILE}}" ) ) {
477 if (tarch::mpi::Rank::getInstance().isGlobalMaster()) {
478 logWarning( "main()", "filter file {{LOG_FILTER_FILE}} not found or file has been corrupted. Use default logging configuration" );
479 }
480 }
481 {% endif %}
482
483 peano4::fillLookupTables();
484 {{NAMESPACE | join("::")}}::{{SUBNAMESPACE}}repositories::DataRepository::initDatatypes();
485 peano4::initSingletons(
486 {{NAMESPACE | join("::")}}::DomainOffset,
487 {{NAMESPACE | join("::")}}::DomainSize,
488 {{NAMESPACE | join("::")}}::PeriodicBC
489 );
490
491 if ( not ::exahype2::configureRuntime(
492 {{TIMEOUT}},
493 {{NUMBER_OF_THREADS}},
494 { {{GPUS|join(",")}} },
495 {{MULTICORE_ORCHESTRATION}}
496 ) ) {
497 return ExitCodeInvalidBuild;
498 }
499
500 {% if FENV_ARGS is defined and FENV_ARGS != "" -%}
501 feenableexcept({{FENV_ARGS}} );
502 {% endif -%}
503
504
505 for (int n = 0; n < NumberOfPatchesToStudy.size(); n++) {
506 runBenchmarks(NumberOfPatchesToStudy[n], NumberOfLaunchingThreads);
507 }
508
509 ::exahype2::shutdownRuntime(
510 {{NAMESPACE | join("::")}}::{{SUBNAMESPACE}}repositories::DataRepository::shutdownDatatypes
511 );
512
513 return ExitCodeSuccess;
514}
515"""
516
517 def __init__(self, project, exahype_project, function_calls, additional_includes):
518 self.project = project
519 self.exahype_project = exahype_project
520 self.function_calls = function_calls
521 self.additional_includes = additional_includes
522
523 def construct_output(self, output, main_name):
524 d = {
525 "SUBDIRECTORIES": list(self.project.subdirectories),
526 "SUBNAMESPACE": self.project._subdirectory + "::" if self.project._subdirectory else "",
527 "TIMEOUT": self.exahype_project._timeout,
528 "NUMBER_OF_THREADS": self.exahype_project._number_of_threads,
529 "MULTICORE_ORCHESTRATION": self.exahype_project._multicore_orchestration,
530 "GPUS": self.exahype_project._gpus,
531 "LOG_FILTER_FILE": self.exahype_project._log_filter_file,
532 "SOLVER_NAME": self.exahype_project._solvers[0].name,
533 "SOLVER_INSTANCE": self.exahype_project._solvers[0].get_name_of_global_instance(),
534 "FUNCTION_CALLS": self.function_calls,
535 "ADDITIONAL_INCLUDES": self.additional_includes
536 }
537
539 headerfile_template = self.HEADER_TEMPLATE,
540 cppfile_template = self.CPP_TEMPLATE,
541 classname = main_name,
542 namespace = self.project.rootnamespace,
543 subdirectory = "./",
544 dictionary = d,
545 default_overwrite = True,
546 )
547
548 output.makefile.add_h_file(main_name + ".h", generated=True)
549 output.makefile.add_cpp_file(main_name + ".cpp", generated=True)
550 output.add(generated_files)
Base class for the code generation steps that turn an ExaHyPE 2 Project's configuration into actual g...
Definition Project.py:35
Optional realisation of exahype2.api.Project.mainfile_generator.
__init__(self, Project project, str function_calls, str additional_includes)
construct_output(self, peano_project)
Add this generator's output to peano_project (a peano4.api.Project-compatible object,...
Adapter that speaks the construct_output(output, main_name) protocol peano4.api.Project....
__init__(self, project, exahype_project, function_calls, additional_includes)
Singleton logger for the Peano 4 Python API.
Definition Logger.py:37