Skip to content

Commit

Permalink
Merge pull request #40564 from makortel/alpakaModuleTypeResolver
Browse files Browse the repository at this point in the history
Enable "alternative module loading" approach for Alpaka modules
  • Loading branch information
cmsbuild authored Feb 1, 2023
2 parents e86ddad + f93cd75 commit 7a9e56d
Show file tree
Hide file tree
Showing 17 changed files with 342 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,29 @@ namespace edm {
typedef edmplugin::PluginFactory<ParameterSetDescriptionFillerBase*(void)> ParameterSetDescriptionFillerPluginFactory;
}

#define DEFINE_FWK_PSET_DESC_FILLER(type) \
#define DEFINE_FWK_PSET_DESC_FILLER_IMPL(type, name, postfix) \
static const edm::ParameterSetDescriptionFillerPluginFactory::PMaker<edm::ParameterSetDescriptionFiller<type> > \
EDM_PLUGIN_SYM(s_filler, __LINE__)(#type)
EDM_PLUGIN_SYM(s_filler##postfix, __LINE__)(name)
//NOTE: Can't do the below since this appears on the same line as another factory and w'ed have two variables with the same name
//DEFINE_EDM_PLUGIN (edm::ParameterSetDescriptionFillerPluginFactory,type,#type)

#define DEFINE_FWK_PSET_DESC_FILLER(type) DEFINE_FWK_PSET_DESC_FILLER_IMPL(type, #type, _0)

// Define another analogous macro to handle the special case of services.

#define DEFINE_DESC_FILLER_FOR_SERVICES(pluginName, serviceType) \
static const edm::ParameterSetDescriptionFillerPluginFactory::PMaker<edm::DescriptionFillerForServices<serviceType> > \
EDM_PLUGIN_SYM(s_filler, __LINE__)(#pluginName)

#define DEFINE_DESC_FILLER_FOR_ESSOURCES(type) \
#define DEFINE_DESC_FILLER_FOR_ESSOURCES_IMPL(type, name) \
static const edm::ParameterSetDescriptionFillerPluginFactory::PMaker<edm::DescriptionFillerForESSources<type> > \
EDM_PLUGIN_SYM(s_filler, __LINE__)(#type)
EDM_PLUGIN_SYM(s_filler, __LINE__)(name)
#define DEFINE_DESC_FILLER_FOR_ESSOURCES(type) DEFINE_DESC_FILLER_FOR_ESSOURCES_IMPL(type, #type)

#define DEFINE_DESC_FILLER_FOR_ESPRODUCERS(type) \
#define DEFINE_DESC_FILLER_FOR_ESPRODUCERS_IMPL(type, name, postfix) \
static const edm::ParameterSetDescriptionFillerPluginFactory::PMaker<edm::DescriptionFillerForESProducers<type> > \
EDM_PLUGIN_SYM(s_filler, __LINE__)(#type)
EDM_PLUGIN_SYM(s_filler##postfix, __LINE__)(name)
#define DEFINE_DESC_FILLER_FOR_ESPRODUCERS(type) DEFINE_DESC_FILLER_FOR_ESPRODUCERS_IMPL(type, #type, _0)

#define DEFINE_DESC_FILLER_FOR_EDLOOPERS(type) \
static const edm::ParameterSetDescriptionFillerPluginFactory::PMaker<edm::DescriptionFillerForEDLoopers<type> > \
Expand Down
3 changes: 3 additions & 0 deletions FWCore/ParameterSet/src/defaultModuleLabel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ namespace edm {
// remove all colons (module type may contain namespace)
label.erase(std::remove(label.begin(), label.end(), ':'), label.end());

// remove everything after first '@' (used for module alternatives)
label.erase(std::find(label.begin(), label.end(), '@'), label.end());

// the following code originates from HLTrigger/HLTcore/interface/defaultModuleLabel.h
// if the label is all uppercase, change it to all lowercase
// if the label starts with more than one uppercase letter, change n-1 to lowercase
Expand Down
12 changes: 10 additions & 2 deletions HeterogeneousCore/AlpakaCore/interface/MakerMacros.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@
#include "FWCore/Framework/interface/MakerMacros.h"
#include "HeterogeneousCore/AlpakaInterface/interface/config.h"

// force expanding ALPAKA_ACCELERATOR_NAMESPACE before stringification inside DEFINE_FWK_MODULE
#define DEFINE_FWK_ALPAKA_MODULE2(name) DEFINE_FWK_MODULE(name)
// force expanding ALPAKA_ACCELERATOR_NAMESPACE before stringification
// use the serial_sync variant for cfi file generation with the type@alpaka C++ type
#ifdef ALPAKA_ACC_CPU_B_SEQ_T_SEQ_ENABLED
#define DEFINE_FWK_ALPAKA_MODULE2(name_ns, name) \
DEFINE_FWK_MODULE(name_ns); \
DEFINE_FWK_PSET_DESC_FILLER_IMPL(name_ns, #name "@alpaka", _1)
#define DEFINE_FWK_ALPAKA_MODULE(name) DEFINE_FWK_ALPAKA_MODULE2(ALPAKA_ACCELERATOR_NAMESPACE::name, name)
#else
#define DEFINE_FWK_ALPAKA_MODULE2(name_ns) DEFINE_FWK_MODULE(name_ns)
#define DEFINE_FWK_ALPAKA_MODULE(name) DEFINE_FWK_ALPAKA_MODULE2(ALPAKA_ACCELERATOR_NAMESPACE::name)
#endif

#endif // HeterogeneousCore_AlpakaCore_interface_MakerMacros_h
7 changes: 7 additions & 0 deletions HeterogeneousCore/AlpakaCore/interface/alpaka/ESProducer.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "FWCore/Framework/interface/ESProducer.h"
#include "FWCore/Framework/interface/produce_helpers.h"
#include "HeterogeneousCore/AlpakaCore/interface/module_backend_config.h"
#include "HeterogeneousCore/AlpakaCore/interface/alpaka/ESDeviceProduct.h"
#include "HeterogeneousCore/AlpakaCore/interface/alpaka/Record.h"
#include "HeterogeneousCore/AlpakaInterface/interface/devices.h"
Expand All @@ -23,6 +24,12 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE {
class ESProducer : public edm::ESProducer {
using Base = edm::ESProducer;

public:
static void prevalidate(edm::ConfigurationDescriptions& descriptions) {
Base::prevalidate(descriptions);
cms::alpakatools::module_backend_config(descriptions);
}

protected:
template <typename T>
auto setWhatProduced(T* iThis, edm::es::Label const& label = {}) {
Expand Down
13 changes: 11 additions & 2 deletions HeterogeneousCore/AlpakaCore/interface/alpaka/ModuleFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@
#include "FWCore/Framework/interface/ModuleFactory.h"
#include "HeterogeneousCore/AlpakaInterface/interface/config.h"

// force expanding ALPAKA_ACCELERATOR_NAMESPACE before stringification inside DEFINE_FWK_EVENTSETUP_MODULE
#define DEFINE_FWK_EVENTSETUP_ALPAKA_MODULE2(type) DEFINE_FWK_EVENTSETUP_MODULE(type)
// force expanding ALPAKA_ACCELERATOR_NAMESPACE before stringification
// use the serial_sync variant for cfi file generation with the type@alpaka C++ type
#ifdef ALPAKA_ACC_CPU_B_SEQ_T_SEQ_ENABLED
#define DEFINE_FWK_EVENTSETUP_ALPAKA_MODULE2(type_ns, type) \
DEFINE_FWK_EVENTSETUP_MODULE(type_ns); \
DEFINE_DESC_FILLER_FOR_ESPRODUCERS_IMPL(type_ns, #type "@alpaka", _1)
#define DEFINE_FWK_EVENTSETUP_ALPAKA_MODULE(type) \
DEFINE_FWK_EVENTSETUP_ALPAKA_MODULE2(ALPAKA_ACCELERATOR_NAMESPACE::type, type)
#else
#define DEFINE_FWK_EVENTSETUP_ALPAKA_MODULE2(type_ns) DEFINE_FWK_EVENTSETUP_MODULE(type_ns)
#define DEFINE_FWK_EVENTSETUP_ALPAKA_MODULE(type) \
DEFINE_FWK_EVENTSETUP_ALPAKA_MODULE2(ALPAKA_ACCELERATOR_NAMESPACE::type)
#endif

#endif
7 changes: 7 additions & 0 deletions HeterogeneousCore/AlpakaCore/interface/alpaka/ProducerBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
#define HeterogeneousCore_AlpakaCore_interface_ProducerBase_h

#include "DataFormats/Common/interface/DeviceProduct.h"
#include "FWCore/Framework/interface/FrameworkfwdMostUsed.h"
#include "FWCore/Framework/interface/moduleAbilities.h"
#include "FWCore/Utilities/interface/EDPutToken.h"
#include "FWCore/Utilities/interface/Transition.h"
#include "HeterogeneousCore/AlpakaCore/interface/alpaka/EDMetadataAcquireSentry.h"
#include "HeterogeneousCore/AlpakaCore/interface/EventCache.h"
#include "HeterogeneousCore/AlpakaCore/interface/QueueCache.h"
#include "HeterogeneousCore/AlpakaCore/interface/module_backend_config.h"
#include "HeterogeneousCore/AlpakaInterface/interface/TransferToHost.h"

#include <memory>
Expand Down Expand Up @@ -51,6 +53,11 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE {
return ProducerBaseAdaptor<ProducerBase, Tr>(*this, std::move(instanceName));
}

static void prevalidate(edm::ConfigurationDescriptions& descriptions) {
Base::prevalidate(descriptions);
cms::alpakatools::module_backend_config(descriptions);
}

private:
template <typename TProducer, edm::Transition Tr>
friend class ProducerBaseAdaptor;
Expand Down
10 changes: 10 additions & 0 deletions HeterogeneousCore/AlpakaCore/interface/module_backend_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef HeterogeneousCore_AlpakaCore_interface_module_backend_config_h
#define HeterogeneousCore_AlpakaCore_interface_module_backend_config_h

#include "FWCore/Framework/interface/FrameworkfwdMostUsed.h"

namespace cms::alpakatools {
void module_backend_config(edm::ConfigurationDescriptions& iDesc);
}

#endif
7 changes: 7 additions & 0 deletions HeterogeneousCore/AlpakaCore/plugins/BuildFile.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<library file="*.cc" name="HeterogeneousCoreAlpakaCorePlugins">
<use name="FWCore/Framework"/>
<use name="FWCore/MessageLogger"/>
<use name="FWCore/ParameterSet"/>
<use name="FWCore/Utilities"/>
<flags EDM_PLUGIN="1"/>
</library>
66 changes: 66 additions & 0 deletions HeterogeneousCore/AlpakaCore/plugins/ModuleTypeResolverAlpaka.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include "FWCore/Framework/interface/ModuleTypeResolverMaker.h"
#include "FWCore/MessageLogger/interface/MessageLogger.h"
#include "FWCore/ParameterSet/interface/ParameterSet.h"
#include "FWCore/Utilities/interface/EDMException.h"
#include "FWCore/Utilities/interface/thread_safety_macros.h"

#include <cassert>
#include <cstring>
#include <string>
#include <unordered_map>

class ModuleTypeResolverAlpaka : public edm::ModuleTypeResolverBase {
public:
ModuleTypeResolverAlpaka(std::string backendPrefix) : backendPrefix_(std::move(backendPrefix)) {}

std::pair<std::string, int> resolveType(std::string basename, int index) const final {
assert(index == kInitialIndex);
constexpr auto kAlpaka = "@alpaka";
auto found = basename.find(kAlpaka);
if (found != std::string::npos) {
if (backendPrefix_.empty()) {
throw edm::Exception(edm::errors::LogicError)
<< "AlpakaModuleTypeResolver encountered a module with type name " << basename
<< " but the backend prefix was empty. This should not happen. Please contact framework developers.";
}
basename.replace(found, std::strlen(kAlpaka), "");
basename = backendPrefix_ + basename;
}
return {basename, kLastIndex};
}

private:
std::string const backendPrefix_;
};

class ModuleTypeResolverMakerAlpaka : public edm::ModuleTypeResolverMaker {
public:
ModuleTypeResolverMakerAlpaka() {}

std::shared_ptr<edm::ModuleTypeResolverBase const> makeResolver(edm::ParameterSet const& modulePSet) const final {
std::string prefix = "";
if (modulePSet.existsAs<edm::ParameterSet>("alpaka", false)) {
auto const& backend =
modulePSet.getUntrackedParameter<edm::ParameterSet>("alpaka").getUntrackedParameter<std::string>("backend");
prefix = fmt::format("alpaka_{}::", backend);

LogDebug("AlpakaModuleTypeResolver")
.format("AlpakaModuleTypeResolver: module {} backend prefix {}",
modulePSet.getParameter<std::string>("@module_label"),
prefix);
}
auto found = cache_.find(prefix);
if (found == cache_.end()) {
bool inserted;
std::tie(found, inserted) = cache_.emplace(prefix, std::make_shared<ModuleTypeResolverAlpaka>(prefix));
}
return found->second;
}

private:
// no protection needed because this object is used only in single-thread context
CMS_SA_ALLOW mutable std::unordered_map<std::string, std::shared_ptr<ModuleTypeResolverAlpaka const>> cache_;
};

#include "FWCore/Framework/interface/ModuleTypeResolverMakerFactory.h"
DEFINE_EDM_PLUGIN(edm::ModuleTypeResolverMakerFactory, ModuleTypeResolverMakerAlpaka, "ModuleTypeResolverAlpaka");
70 changes: 70 additions & 0 deletions HeterogeneousCore/AlpakaCore/python/ProcessAcceleratorAlpaka.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import FWCore.ParameterSet.Config as cms

class ModuleTypeResolverAlpaka:
def __init__(self, accelerators, backend):
# first element is used as the default is nothing is set
self._valid_backends = []
if "gpu-nvidia" in accelerators:
self._valid_backends.append("cuda_async")
if "cpu" in accelerators:
self._valid_backends.append("serial_sync")
if len(self._valid_backends) == 0:
raise cms.EDMException(cms.edm.errors.UnavailableAccelerator, "ModuleTypeResolverAlpaka had no backends available because of the combination of the job configuration and accelerator availability of on the machine. The job sees {} accelerators.".format(", ".join(accelerators)))
if backend is not None:
if not backend in self._valid_backends:
raise cms.EDMException(cms.edm.errors.UnavailableAccelerator, "The ProcessAcceleratorAlpaka was configured to use {} backend, but that backend is not available because of the combination of the job configuration and accelerator availability on the machine. The job was configured to use {} accelerators, which translates to {} Alpaka backends.".format(
backend, ", ".join(accelerators), ", ".join(self._valid_backends)))
if backend != self._valid_backends[0]:
self._valid_backends.remove(backend)
self._valid_backends.insert(0, backend)

def plugin(self):
return "ModuleTypeResolverAlpaka"

def setModuleVariant(self, module):
if module.type_().endswith("@alpaka"):
defaultBackend = self._valid_backends[0]
if hasattr(module, "alpaka"):
if hasattr(module.alpaka, "backend"):
if module.alpaka.backend == "":
module.alpaka.backend = defaultBackend
elif module.alpaka.backend.value() not in self._valid_backends:
raise cms.EDMException(cms.edm.errors.UnavailableAccelerator, "Module {} has the Alpaka backend set explicitly, but its accelerator is not available for the job because of the combination of the job configuration and accelerator availability on the machine. The following Alpaka backends are available for the job {}.".format(module.label_(), ", ".join(self._valid_backends)))
else:
module.alpaka.backend = cms.untracked.string(defaultBackend)
else:
module.alpaka = cms.untracked.PSet(
backend = cms.untracked.string(defaultBackend)
)

class ProcessAcceleratorAlpaka(cms.ProcessAccelerator):
"""ProcessAcceleratorAlpaka itself does not define or inspect
availability of any accelerator devices. It merely sets up
necessary Alpaka infrastructure based on the availability of
accelerators that the concrete ProcessAccelerators (like
ProcessAcceleratorCUDA) define.
"""
def __init__(self):
super(ProcessAcceleratorAlpaka,self).__init__()
self._backend = None
# User-facing interface
def setBackend(self, backend):
self._backend = backend
# Framework-facing interface
def moduleTypeResolver(self, accelerators):
return ModuleTypeResolverAlpaka(accelerators, self._backend)
def apply(self, process, accelerators):
if not hasattr(process, "AlpakaServiceSerialSync"):
from HeterogeneousCore.AlpakaServices.AlpakaServiceSerialSync_cfi import AlpakaServiceSerialSync
process.add_(AlpakaServiceSerialSync)
if not hasattr(process, "AlpakaServiceCudaAsync"):
from HeterogeneousCore.AlpakaServices.AlpakaServiceCudaAsync_cfi import AlpakaServiceCudaAsync
process.add_(AlpakaServiceCudaAsync)

if not hasattr(process.MessageLogger, "AlpakaService"):
process.MessageLogger.AlpakaService = cms.untracked.PSet()

process.AlpakaServiceSerialSync.enabled = "cpu" in accelerators
process.AlpakaServiceCudaAsync.enabled = "gpu-nvidia" in accelerators

cms.specialImportRegistry.registerSpecialImportForType(ProcessAcceleratorAlpaka, "from HeterogeneousCore.AlpakaCore.ProcessAcceleratorAlpaka import ProcessAcceleratorAlpaka")
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from HeterogeneousCore.AlpakaCore.ProcessAcceleratorAlpaka import ProcessAcceleratorAlpaka as _ProcessAcceleratorAlpaka

ProcessAcceleratorAlpaka = _ProcessAcceleratorAlpaka()
35 changes: 35 additions & 0 deletions HeterogeneousCore/AlpakaCore/src/module_backend_config.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h"
#include "FWCore/ParameterSet/interface/ParameterSetDescription.h"
#include "HeterogeneousCore/AlpakaCore/interface/module_backend_config.h"

namespace {
const std::string kPSetName("alpaka");
const char* const kComment =
"PSet allows to override the Alpaka backend per module instance. Has an effect only when the module class name "
"has '@alpaka' suffix, i.e. has no effect when the Alpaka backend namespace is used explicitly.";
} // namespace

namespace cms::alpakatools {
void module_backend_config(edm::ConfigurationDescriptions& iDesc) {
// the code below leads to 'alpaka = untracked.PSet(backend = untracked.string)' to be added to the generated cfi files
// TODO: I don't know if this is a desired behavior for HLT
edm::ParameterSetDescription descAlpaka;
descAlpaka.addUntracked<std::string>("backend", "")
->setComment(
"Alpaka backend for this module. Can be empty string (for the global default), 'serial_sync', or "
"'cuda_async'");

if (iDesc.defaultDescription()) {
if (iDesc.defaultDescription()->isLabelUnused(kPSetName)) {
iDesc.defaultDescription()
->addUntracked<edm::ParameterSetDescription>(kPSetName, descAlpaka)
->setComment(kComment);
}
}
for (auto& v : iDesc) {
if (v.second.isLabelUnused(kPSetName)) {
v.second.addUntracked<edm::ParameterSetDescription>(kPSetName, descAlpaka)->setComment(kComment);
}
}
}
} // namespace cms::alpakatools
12 changes: 11 additions & 1 deletion HeterogeneousCore/AlpakaTest/plugins/TestAlpakaAnalyzer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,21 @@ namespace {
class TestAlpakaAnalyzer : public edm::stream::EDAnalyzer<> {
public:
TestAlpakaAnalyzer(edm::ParameterSet const& config)
: source_{config.getParameter<edm::InputTag>("source")}, token_{consumes(source_)} {}
: source_{config.getParameter<edm::InputTag>("source")},
token_{consumes(source_)},
expectSize_(config.getParameter<int>("expectSize")) {}

void analyze(edm::Event const& event, edm::EventSetup const&) override {
portabletest::TestHostCollection const& product = event.get(token_);
auto const& view = product.const_view();
auto& mview = product.view();
auto const& cmview = product.view();

if (expectSize_ >= 0 and expectSize_ != view.metadata().size()) {
throw cms::Exception("Assert") << "Expected input collection size " << expectSize_ << ", got "
<< view.metadata().size();
}

{
edm::LogInfo msg("TestAlpakaAnalyzer");
msg << source_.encode() << ".size() = " << view.metadata().size() << '\n';
Expand Down Expand Up @@ -132,12 +139,15 @@ class TestAlpakaAnalyzer : public edm::stream::EDAnalyzer<> {
static void fillDescriptions(edm::ConfigurationDescriptions& descriptions) {
edm::ParameterSetDescription desc;
desc.add<edm::InputTag>("source");
desc.add<int>("expectSize", -1)
->setComment("Expected size of the input collection. Values < 0 mean the check is not performed. Default: -1");
descriptions.addWithDefaultLabel(desc);
}

private:
const edm::InputTag source_;
const edm::EDGetTokenT<portabletest::TestHostCollection> token_;
const int expectSize_;
};

#include "FWCore/Framework/interface/MakerMacros.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE {
class TestAlpakaGlobalProducer : public global::EDProducer<> {
public:
TestAlpakaGlobalProducer(edm::ParameterSet const& config)
: esToken_(esConsumes()), deviceToken_{produces()}, size_{config.getParameter<int32_t>("size")} {}
: esToken_(esConsumes()),
deviceToken_{produces()},
size_{config.getParameter<edm::ParameterSet>("size").getParameter<int32_t>(
EDM_STRINGIZE(ALPAKA_ACCELERATOR_NAMESPACE))} {}

void produce(edm::StreamID, device::Event& iEvent, device::EventSetup const& iSetup) const override {
[[maybe_unused]] auto const& esData = iSetup.getData(esToken_);
Expand All @@ -36,7 +39,12 @@ namespace ALPAKA_ACCELERATOR_NAMESPACE {

static void fillDescriptions(edm::ConfigurationDescriptions& descriptions) {
edm::ParameterSetDescription desc;
desc.add<int32_t>("size");

edm::ParameterSetDescription psetSize;
psetSize.add<int32_t>("alpaka_serial_sync");
psetSize.add<int32_t>("alpaka_cuda_async");
desc.add("size", psetSize);

descriptions.addWithDefaultLabel(desc);
}

Expand Down
Loading

0 comments on commit 7a9e56d

Please sign in to comment.