Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a8abc89
created ModelArrayStore for host device sync across ModelComponent bo…
Thanduriel Oct 24, 2025
3414c0f
fixed asserts
Thanduriel Nov 3, 2025
84bbcbd
added field resizing in ModelArrayStore
Thanduriel Nov 3, 2025
5c613b0
adapted ModelArrayRef test for ModelArrayAccessor
Thanduriel Nov 4, 2025
02cce10
added test for device usage with ModelArrayAccessor
Thanduriel Nov 5, 2025
7e24787
limited lifetime of the global ModelArrayStore
Thanduriel Nov 6, 2025
16991c7
[WIP] updated core to use ModelArrayAccessor
Thanduriel Nov 6, 2025
6c0ccc7
[WIP] updated physics to use ModelArrayAccessor
Thanduriel Nov 10, 2025
9c3d00d
updated SlabOcean test
Thanduriel Nov 10, 2025
783e5d1
updated all tests to use ModelArrayAccessor
Thanduriel Nov 11, 2025
d8bf580
added assertions
Thanduriel Nov 12, 2025
6db27ed
use correct sss field in TOPAZ update
Thanduriel Nov 12, 2025
127b113
fixed WITH_KOKKOS switch
Thanduriel Nov 12, 2025
eaf74cd
perform ThermoWinton update on device
Thanduriel Nov 12, 2025
d83837c
eliminated data transfers for advection
Thanduriel Nov 13, 2025
c94510e
working tests with Kokkos enabled
Thanduriel Nov 13, 2025
3b14d3e
fixed crash of testPrognosticData on finalize in release builds
Thanduriel Nov 14, 2025
5659544
created abstractions to use same code w/out kokkos
Thanduriel Nov 14, 2025
23e8a6e
ported HiblerSpread to device
Thanduriel Dec 22, 2025
f01c72b
ported ConstantHealing to device
Thanduriel Dec 22, 2025
abebce6
ported SlabOcean to device
Thanduriel Dec 22, 2025
9c4d1c0
ported ThermoIce0 to device
Thanduriel Dec 23, 2025
d436daa
replaced remaining std funcs in ThermoIce0
Thanduriel Dec 23, 2025
fa0ac76
ported computeGradientOfSeaSurfaceHeight to device
Thanduriel Jan 9, 2026
0a99772
pre-allocate all SSH gradient fields
Thanduriel Jan 9, 2026
c6c0050
corrections to comments of ssh gradient computation
Thanduriel Jan 12, 2026
ff26e6a
share device buffers for advected fields between thermodynamics and …
Thanduriel Jan 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ set(BaseSources
"StructureFactory.cpp"
"ModelArray.cpp"
"ModelArraySlice.cpp"
"ModelArrayStore.cpp"
"ModelComponent.cpp"
"ModelMetadata.cpp"
"NetcdfMetadataConfiguration.cpp"
Expand Down Expand Up @@ -61,6 +62,10 @@ if(ENABLE_MPI)
list(APPEND NextsimSourcesTemp "${ParallelNetCDFSources}")
endif()

if(WITH_KOKKOS)
list(APPEND NextsimSourcesTemp "${CMAKE_CURRENT_SOURCE_DIR}/kokkos/KokkosUtils.cpp")
endif()

set(NextsimSources "${NextsimSourcesTemp}" PARENT_SCOPE)

set(NextsimIncludeDirs
Expand Down
9 changes: 9 additions & 0 deletions core/src/FieldAdvection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,13 @@ ModelArray& FieldAdvection::advectField(
return field;
}

#ifdef USE_KOKKOS
void FieldAdvection::advectField(
const DeviceViewMA& field, const TimestepTime& tst, double lowerLimit, double upperLimit)
{
Module::getImplementation<IDynamics>().advectField(
tst.step.seconds(), field, lowerLimit, upperLimit);
}
#endif

} /* namespace Nextsim */
2 changes: 2 additions & 0 deletions core/src/Model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ Model::HelpMap& Model::getHelpRecursive(HelpMap& map, bool getAll)

void Model::run()
{
assert(Nextsim::ModelComponent::getStore().checkAllRegistered());

try {
iterator.run();
} catch (const std::exception& e) {
Expand Down
103 changes: 103 additions & 0 deletions core/src/ModelArrayStore.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*!
* @author Robert Jendersie <[email protected]>
*/

#include "include/ModelArrayStore.hpp"
#include "include/Logged.hpp"
#include <iostream>
namespace Nextsim {

#ifdef USE_KOKKOS

//double ConstDeviceModelArray::operator[](DeviceIndex i) const { return m_deviceView(i, 0); }
ConstDeviceModelArray::operator ConstDeviceViewMA() const { return m_deviceView; }

//double& DeviceModelArray::operator[](DeviceIndex i) const { return m_deviceView(i, 0); }
DeviceModelArray::operator const DeviceViewMA&() const { return m_deviceView; }

/*************************************************************/
HostViewMA ModelArrayStore::ExtModelArray::hostView()
{
assert(modelArray.trueSize() > 0 && "ModelArray is allocated");
return makeKokkosHostView(modelArray.getDataRef());
}

const DeviceViewMA& ModelArrayStore::ExtModelArray::deviceView()
{
assert(modelArray.trueSize() > 0 && "ModelArray is allocated");
if (!m_deviceModelArray.m_deviceView.is_allocated()) {
m_deviceModelArray.m_deviceView = makeKokkosDeviceView(name, modelArray.getDataRef());
}

return m_deviceModelArray.m_deviceView;
}

DeviceModelArray& ModelArrayStore::ExtModelArray::deviceModelArray()
{
// handles initialization
deviceView();
return m_deviceModelArray;
}
#endif

/*************************************************************/
ModelArrayStore::ExtModelArrayFlagged::ExtModelArrayFlagged(
const std::string& name, bool _isReadWrite, bool _isRegistered)
: isReadWrite(_isReadWrite)
, isRegistered(_isRegistered)
{
extModelArray.name = name;
}

/*************************************************************/
std::unordered_map<std::string, const ModelArray*> ModelArrayStore::getAllData() const
{
std::unordered_map<std::string, const ModelArray*> dataMap;

for (const auto& [name, extArrFlagged] : store) {
dataMap.emplace(name, &extArrFlagged.extModelArray.modelArray);
}

return dataMap;
}

bool ModelArrayStore::checkAllRegistered() const
{
bool b = true;
for (const auto& [_, extArrFlagged] : store) {
if (!extArrFlagged.isRegistered) {
Logged::warning("Field \"" + extArrFlagged.extModelArray.name
+ "\" was not properly initialized by a registering ModelArrayAccessor.\n");
b = false;
}
}

return b;
}

ModelArrayStore::ExtModelArray& ModelArrayStore::getRW(const std::string& field)
{
if (auto it = store.find(field); it != store.end()) {
ExtModelArrayFlagged& extArrFlagged = it->second;
if (!extArrFlagged.isReadWrite) {
if (extArrFlagged.isRegistered) {
throw std::logic_error(
"Trying to access the read-only ModelArray \"" + field + "\" as read-write.");
} else {
// promote to read-write because we don't now the true restriction yet
extArrFlagged.isReadWrite = true;
}
}
return extArrFlagged.extModelArray;
}

// Regular emplace would be fine here since we know that it does not exist but try_emplace
// has a more ergonomic signature.
return store.try_emplace(field, field, RW, false).first->second.extModelArray;
}

ModelArrayStore::ExtModelArray& ModelArrayStore::getRO(const std::string& field)
{
return store.try_emplace(field, field, RO, false).first->second.extModelArray;
}
}
29 changes: 29 additions & 0 deletions core/src/ModelComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ namespace Nextsim {

size_t ModelComponent::nOcean = 0;
std::vector<size_t> ModelComponent::oceanIndex;
bool ModelComponent::columnPhysicsStoreIsDestroyed; // initialized to 0 because of static lifetime

#if USE_KOKKOS
KokkosDeviceMapView<DeviceIndex> ModelComponent::oceanIndexDevice;
#endif

ModelComponent::ModelComponent()
{
Expand Down Expand Up @@ -38,6 +43,10 @@ void ModelComponent::setOceanMask(const ModelArray& mask)
oceanIndex[iOceanIndex++] = i;
}
}

#if USE_KOKKOS
makeOceanIndexDevice();
#endif
}

// Fills the nOcean and OceanIndex variables for the zero land case
Expand All @@ -53,6 +62,10 @@ void ModelComponent::noLandMask()
for (size_t i = 0; i < ModelArray::size(ModelArray::Type::H); ++i) {
oceanIndex[i] = i;
}

#if USE_KOKKOS
makeOceanIndexDevice();
#endif
}

ModelArray ModelComponent::mask(const ModelArray& data, const double missingValue)
Expand All @@ -67,4 +80,20 @@ ModelArray ModelComponent::mask(const ModelArray& data, const double missingValu

const ModelArray& ModelComponent::oceanMask() { return oceanMaskSingleton(); }

#if USE_KOKKOS
void ModelComponent::makeOceanIndexDevice()
{
// some tests don't need Kokkos
if (!Kokkos::is_initialized()) {
return;
}

std::vector<DeviceIndex> buf(oceanIndex.begin(), oceanIndex.end());
oceanIndexDevice = makeKokkosDeviceViewMap("oceanIndex", buf, MakeViewOptions::ALWAYS_COPY);
Finalizer::registerUnique(destroyOceanIndex);
}

void ModelComponent::destroyOceanIndex() { oceanIndexDevice.assign_data(nullptr); }
#endif

} /* namespace Nextsim */
31 changes: 15 additions & 16 deletions core/src/PrognosticData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

#include "include/FieldAdvection.hpp"
#include "include/Finalizer.hpp"
#include "include/ModelArrayRef.hpp"
#include "include/NextsimModule.hpp"
#include "include/gridNames.hpp"

Expand All @@ -23,19 +22,15 @@ static constexpr bool checkFieldsFastDefault = true;

PrognosticData::PrognosticData()
: m_dt(1)
, hice(ModelArray::AdvectionType, { 0, 50 })
, cice(ModelArray::AdvectionType, { 0, 1 })
, damage(ModelArray::AdvectionType, { 0, 1 })
, hsnow(ModelArray::AdvectionType, { 0, 10 })
, hiceAccessor(getStore(), RW, ModelArray::AdvectionType, std::pair(0.0, 50.0))
, ciceAccessor(getStore(), RW, ModelArray::AdvectionType, std::pair(0.0, 1.0))
, damageAccessor(getStore(), RW, ModelArray::AdvectionType, std::pair(0.0, 1.0))
, hsnowAccessor(getStore(), RW, ModelArray::AdvectionType, std::pair(0.0, 10.0))
, pAtmBdy(nullptr)
, pOcnBdy(nullptr)
, pDynamics(nullptr)
{
getStore().registerArray(Shared::DAMAGE, &damage, RW);
getStore().registerArray(Shared::H_ICE_DG, &hice, RW);
getStore().registerArray(Shared::C_ICE_DG, &cice, RW);
getStore().registerArray(Shared::H_SNOW_DG, &hsnow, RW);
}
}

void PrognosticData::configure()
{
Expand Down Expand Up @@ -64,8 +59,8 @@ void PrognosticData::configure()
}
} else if (checkFast) {
addChecks({
{ "thickness", &hice },
{ "concentration", &cice },
{ "thickness", &hiceAccessor.getHostRO() },
{ "concentration", &ciceAccessor.getHostRO() },
});
}
}
Expand All @@ -82,7 +77,11 @@ void copyMeanComponent(const ModelArray& source, ModelArray& sink)

void PrognosticData::setData(const ModelState::DataMap& ms)
{

AdvectedField& hice = hiceAccessor.getHostRW();
AdvectedField& cice = ciceAccessor.getHostRW();
AdvectedField& hsnow = hsnowAccessor.getHostRW();
AdvectedField& damage = damageAccessor.getHostRW();

if (ms.count(maskName)) {
setOceanMask(ms.at(maskName));
} else {
Expand Down Expand Up @@ -154,9 +153,9 @@ ModelState PrognosticData::getStatePrognostic() const
{
ModelState state = { {
{ maskName, ModelArray(oceanMask()) }, // make a copy
{ hiceName, hice },
{ ciceName, cice },
{ hsnowName, hsnow },
{ hiceName, hiceAccessor.getHostRO() },
{ ciceName, ciceAccessor.getHostRO() },
{ hsnowName, hsnowAccessor.getHostRO() },
},
ModelComponent::getConfiguration() };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ enum class Type {

enum class Base { Cell, Vertex };

static const Type AdvectionType = Type::DG;
static constexpr Type AdvectionType = Type::DG;

static ModelArray HField() { return ModelArray(Type::H); }
static ModelArray VertexField() { return ModelArray(Type::VERTEX); }
Expand Down
16 changes: 16 additions & 0 deletions core/src/include/FieldAdvection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#include "include/ModelArray.hpp"
#include "include/Time.hpp"

#ifdef USE_KOKKOS
#include "include/ModelArrayStore.hpp" // needed for DeviceViewMA
#endif

namespace Nextsim {

class FieldAdvection {
Expand All @@ -26,6 +30,18 @@ class FieldAdvection {
static ModelArray& advectField(ModelArray& field, const TimestepTime& tst,
double lowerLimit = -std::numeric_limits<double>::infinity(),
double upperLimit = std::numeric_limits<double>::infinity());

#ifdef USE_KOKKOS
static void advectField(const DeviceViewMA& field, const TimestepTime& tst,
double lowerLimit = -std::numeric_limits<double>::infinity(),
double upperLimit = std::numeric_limits<double>::infinity());

/* template<typename ExecSpace>
static void advectField(ExecSpace execSpace, const DeviceViewMA& field, const TimestepTime& tst,
double lowerLimit = -std::numeric_limits<double>::infinity(),
double upperLimit = std::numeric_limits<double>::infinity())
{}*/
#endif
};

} /* namespace Nextsim */
Expand Down
51 changes: 51 additions & 0 deletions core/src/include/KernelAlternatives.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*!
* @author Robert Jendersie <[email protected]>
*/

#ifndef KERNEL_ALTERNATIVES_HPP
#define KERNEL_ALTERNATIVES_HPP

#ifdef USE_KOKKOS
#include <Kokkos_Core.hpp>
#include "../kokkos/include/KokkosUtils.hpp"
#else
#include <cmath>
#include <algorithm>
#endif

namespace Nextsim {

// namespace Utils contains standard math functions (sin, sqrt, min, ...)
#ifdef USE_KOKKOS
namespace Utils = Kokkos;
#else
namespace Utils = std;
#endif

// KERNEL_IMPL_FUNCTION annotates functions that need to be callable inside a kernel
#ifdef USE_KOKKOS
#define KERNEL_IMPL_FUNCTION KOKKOS_IMPL_FUNCTION
#else
// no special annotation needed
#define KERNEL_IMPL_FUNCTION
#endif

// KERNEL_LAMBDA capture list of a lambda used in a overElements call
#ifdef USE_KOKKOS
#define OVER_ELEMENTS_LAMBDA KOKKOS_LAMBDA
using ElementIndex = DeviceIndex;
#else
#define OVER_ELEMENTS_LAMBDA [&]
using ElementIndex = size_t;
#endif

// Execution space that is used to signal async operations.
#ifdef USE_KOKKOS
using DefaultExecutionSpace = Kokkos::DefaultExecutionSpace;
#else
struct ExecutionSpaceDummy {};
using DefaultExecutionSpace = ExecutionSpaceDummy;
#endif
}

#endif /* KERNEL_ALTERNATIVES_HPP */
Loading
Loading