Files
mfem/tests/benchmarks/bench_ceed.cpp
T
2026-01-14 14:57:07 -08:00

367 lines
10 KiB
C++

// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
//
//
// This benchmark contains the implementation of the CEED's bake-off problems:
// high-order kernels/benchmarks designed to test and compare the performance
// of high-order codes.
//
// See: https://ceed.exascaleproject.org/bps
#include "bench.hpp" // IWYU pragma: keep
#ifdef MFEM_USE_BENCHMARK
#include <cassert>
#include <string>
#include "fem/qinterp/det.hpp" // IWYU pragma: keep
#include "fem/qinterp/grad.hpp" // IWYU pragma: keep
#include "fem/integ/lininteg_domain_kernels.hpp" // IWYU pragma: keep
#include "fem/integ/bilininteg_vecdiffusion_pa.hpp" // IWYU pragma: keep
// Custom benchmark arguments generator
static void CustomArguments(bmi::Benchmark *b) noexcept
{
constexpr int MAX_NDOFS = 16 * 1024 * (mfem_use_gpu ? 1024 : 8);
const auto orders = { 7, 6, 5, 4, 3, 2, 1 };
constexpr auto ndofs = [](int n) constexpr noexcept -> int
{
return (n + 1) * (n + 1) * (n + 1);
};
constexpr auto inc = [](int n) constexpr noexcept -> int
{
return n < 160 ? 4 : n < 240 ? 8 : n < 320 ? 16 : 32;
};
for (auto p : orders)
{
for (int n = 16; ndofs(n) <= MAX_NDOFS; n += inc(n))
{
b->Args({p, n});
}
}
}
// Register kernel specializations used in the benchmarks
static void AddKernelSpecializations()
{
using DET = QuadratureInterpolator::DetKernels;
DET::Specialization<3, 3, 2, 2>::Add();
DET::Specialization<3, 3, 2, 3>::Add();
DET::Specialization<3, 3, 2, 5>::Add();
DET::Specialization<3, 3, 2, 6>::Add();
DET::Specialization<3, 3, 5, 5>::Add();
// Others might exceed memory limits
using GRAD = QuadratureInterpolator::GradKernels;
GRAD::Specialization<3, QVectorLayout::byNODES, false, 3, 2, 2>::Add();
GRAD::Specialization<3, QVectorLayout::byNODES, false, 3, 2, 7>::Add();
GRAD::Specialization<3, QVectorLayout::byNODES, false, 3, 2, 8>::Add();
GRAD::Specialization<3, QVectorLayout::byNODES, false, 3, 2, 9>::Add();
using LIN = DomainLFIntegrator::AssembleKernels;
LIN::Specialization<3, 7, 7>::Add();
LIN::Specialization<3, 6, 6>::Add();
LIN::Specialization<3, 8, 8>::Add();
using VDIFF = VectorDiffusionIntegrator::ApplyPAKernels;
VDIFF::Specialization<3, 3, 3, 3>::Add();
VDIFF::Specialization<3, 3, 4, 4>::Add();
VDIFF::Specialization<3, 3, 5, 5>::Add();
VDIFF::Specialization<3, 3, 6, 6>::Add();
VDIFF::Specialization<3, 3, 7, 7>::Add();
VDIFF::Specialization<3, 3, 8, 8>::Add();
}
// Bake-off base class
template <int BFI, int VDIM, bool GLL>
struct BakeOff
{
inline static constexpr int DIM = 3;
const int p, c, q, n, nx, ny, nz;
Mesh mesh;
H1_FECollection fec;
FiniteElementSpace fes;
const Geometry::Type geom_type;
IntegrationRules irs;
const IntegrationRule *ir;
ConstantCoefficient one;
Vector uvec;
VectorConstantCoefficient unit_vec;
const int dofs;
GridFunction x, y;
BilinearForm a;
double mdofs{};
BilinearFormIntegrator *bfi;
BakeOff(int p, int side):
p(p), c(side), q(2 * p + (GLL ? -1 : 3)),
n((assert(c >= p), c / p)),
nx(n + (p * (n + 1) * p * n * p * n < c * c * c ? 1 : 0)),
ny(n + (p * (n + 1) * p * (n + 1) * p * n < c * c * c ? 1 : 0)),
nz(n),
mesh(Mesh::MakeCartesian3D(nx, ny, nz, Element::HEXAHEDRON)),
fec(p, DIM, BasisType::GaussLobatto),
fes(&mesh, &fec, VDIM, VDIM == 3 ? Ordering::byVDIM : Ordering::byNODES),
geom_type(mesh.GetTypicalElementGeometry()),
irs(0, GLL ? Quadrature1D::GaussLobatto : Quadrature1D::GaussLegendre),
ir(&irs.Get(geom_type, q)),
one(1.0),
uvec(DIM),
unit_vec((uvec = 1.0, uvec /= uvec.Norml2(), uvec)),
dofs(fes.GetTrueVSize()),
x(&fes),
y(&fes),
a(&fes)
{
x = 0.0;
if constexpr (BFI == 1)
{
bfi = new MassIntegrator(one, ir);
}
else if constexpr (BFI == 2)
{
bfi = new VectorMassIntegrator(one, ir);
}
else if constexpr (BFI == 3 || BFI == 5)
{
bfi = new DiffusionIntegrator(one, ir);
}
else if constexpr (BFI == 4 || BFI == 6)
{
bfi = new VectorDiffusionIntegrator(one, ir);
}
else
{
static_assert(BFI >= 1 && BFI <= 6, "Invalid BilinearFormIntegrator");
}
a.AddDomainIntegrator(bfi);
}
virtual void benchmark() = 0;
[[nodiscard]] double SumMdofs() const noexcept { return mdofs; }
[[nodiscard]] double MDofs() const noexcept { return 1e-6 * dofs; }
};
// Bake-off Problems (BPs)
template <int BFI, int VDIM, bool GLL>
struct BP : public BakeOff<BFI, VDIM, GLL>
{
const int max_it = 32, print_lvl = -1;
Array<int> ess_tdof_list;
Array<int> ess_bdr;
LinearForm b;
OperatorPtr A;
Vector B, X;
CGSolver cg;
using base = BakeOff<BFI, VDIM, GLL>;
using base::a;
using base::ir;
using base::one;
using base::mesh;
using base::fes;
using base::x;
using base::y;
using base::mdofs;
using base::unit_vec;
using base::bfi;
BP(int p, int side) noexcept: base(p, side),
ess_bdr(mesh.bdr_attributes.Max()),
b(&fes)
{
ess_bdr = 1;
fes.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
if constexpr (VDIM == 1)
{
b.AddDomainIntegrator(new DomainLFIntegrator(one));
}
else
{
b.AddDomainIntegrator(new VectorDomainLFIntegrator(unit_vec));
}
b.UseFastAssembly(true);
b.Assemble();
a.SetAssemblyLevel(AssemblyLevel::PARTIAL);
a.Assemble();
a.FormLinearSystem(ess_tdof_list, x, b, A, X, B);
cg.SetOperator(*A);
cg.SetAbsTol(0.0);
cg.iterative_mode = false;
{
cg.SetPrintLevel(-1);
cg.SetMaxIter(1000);
cg.SetRelTol(1e-8);
cg.Mult(B, X);
MFEM_VERIFY(cg.GetConverged(), "CG solver did not converge!");
}
cg.SetRelTol(0.0);
cg.SetMaxIter(max_it);
cg.SetPrintLevel(print_lvl);
benchmark();
mdofs = 0.0;
}
void benchmark() override
{
cg.Mult(B, X);
MFEM_DEVICE_SYNC;
mdofs += this->MDofs() * cg.GetNumIterations();
}
};
// Bake-off Kernels (BKs)
template <int BFI, int VDIM, bool GLL>
struct BK : public BakeOff<BFI, VDIM, GLL>
{
Vector xe, ye;
using base = BakeOff<BFI, VDIM, GLL>;
using base::ir;
using base::one;
using base::bfi;
using base::fes;
using base::mdofs;
BK(int order, int side) noexcept: base(order, side)
{
bfi->AssemblePA(fes);
const Table &el2dof = fes.GetElementToDofTable();
const int e_size = el2dof.Size_of_connections()*fes.GetVDim();
const auto R = fes.GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
MFEM_VERIFY(e_size == R->Height(), "Input/Output E-vector size mismatch!");
xe.SetSize(R->Height());
ye.SetSize(R->Height());
xe.UseDevice(true);
ye.UseDevice(true);
xe.Randomize(1);
xe.Read();
ye = 0.0;
benchmark();
mdofs = 0.0;
}
void benchmark() override
{
bfi->AddMultPA(xe, ye);
MFEM_DEVICE_SYNC;
mdofs += this->MDofs();
}
};
// Benchmarks
template <typename T>
static void Benchmark(bm::State& state) noexcept
{
T run(state.range(0), state.range(1));
while (state.KeepRunning()) { run.benchmark(); }
state.counters["Dofs"] = bm::Counter(run.dofs);
state.counters["MDof/s"] = bm::Counter(run.SumMdofs(), bm::Counter::kIsRate);
state.counters["Order"] = bm::Counter(state.range(0));
}
#define REGISTER(PK, BFI, VDIM, GLL) \
BENCHMARK_TEMPLATE(Benchmark, PK<BFI, VDIM, GLL>) \
->Name(#PK #BFI)->Apply(CustomArguments)->Unit(bm::kMillisecond)
// BP1: scalar PCG with mass matrix, q=p+2
REGISTER(BP, 1, 1, false);
// BP2: vector PCG with mass matrix, q=p+2
REGISTER(BP, 2, 3, false);
// BP3: scalar PCG with stiffness matrix, q=p+2
REGISTER(BP, 3, 1, false);
// BP4: vector PCG with stiffness matrix, q=p+2
REGISTER(BP, 4, 3, false);
// BP5: scalar PCG with stiffness matrix, q=p+1
REGISTER(BP, 5, 1, true);
// BP6: vector PCG with stiffness matrix, q=p+1
REGISTER(BP, 6, 3, true);
// BK1: scalar E-vector-to-E-vector evaluation of mass matrix, q=p+2
REGISTER(BK, 1, 1, false);
// BK2: vector E-vector-to-E-vector evaluation of mass matrix, q=p+2
REGISTER(BK, 2, 3, false);
// BK3: scalar E-vector-to-E-vector evaluation of stiffness matrix, q=p+2
REGISTER(BK, 3, 1, false);
// BK4: vector E-vector-to-E-vector evaluation of stiffness matrix, q=p+2
REGISTER(BK, 4, 3, false);
// BK5: scalar E-vector-to-E-vector evaluation of stiffness matrix, q=p+1
REGISTER(BK, 5, 1, true);
// BK6: vector E-vector-to-E-vector evaluation of stiffness matrix, q=p+1
REGISTER(BK, 6, 3, true);
/**
* @brief CEED Bake-off Problems main entry point
* Command line options:
* --benchmark_context=device=gpu
* --benchmark_filter=BP1
* --benchmark_out_format=csv
* --benchmark_out=bp1.csv
*/
int main(int argc, char *argv[])
{
bm::ConsoleReporter CR;
bm::Initialize(&argc, argv);
AddKernelSpecializations();
// Device setup, cpu by default
std::string device_config = "cpu";
auto global_context = bmi::GetGlobalContext();
if (global_context != nullptr)
{
const auto device = global_context->find("device");
if (device != global_context->end())
{
mfem::out << device->first << " : " << device->second << std::endl;
device_config = device->second;
}
}
Device device(device_config.c_str());
device.Print();
if (bm::ReportUnrecognizedArguments(argc, argv)) { return EXIT_FAILURE; }
bm::RunSpecifiedBenchmarks(&CR);
bm::Shutdown();
return EXIT_SUCCESS;
}
#endif // MFEM_USE_BENCHMARK