diff --git a/part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/L1TMLDemo_emulator_v1.cpp b/part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/L1TMLDemo_emulator_v1.cpp index c3b488846a0b1448659e28fb9a5a3795a74038c0..2145b667734edc84b5473b54c3f1d7078c8b9d47 100644 --- a/part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/L1TMLDemo_emulator_v1.cpp +++ b/part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/L1TMLDemo_emulator_v1.cpp @@ -9,7 +9,8 @@ HLS4ML Emulator Class class L1TMLDemo_emulator_v1 : public hls4mlEmulator::Model { private: - static const int N_INPUT=2; // TODO update this to real model + // Note: these need to match the defined model + static const int N_INPUT=26; static const int N_OUTPUT=1; input_t _input[N_INPUT]; result_t _result[N_OUTPUT]; @@ -32,7 +33,7 @@ public: virtual void read_result(std::any result) { // copy result result_t *result_p = std::any_cast<result_t*>(result); - result_p[0] = _result; + *result_p = _result; } }; diff --git a/part3/cmssw/src/L1Trigger/L1TMLDemo/plugins/BuildFile.xml b/part3/cmssw/src/L1Trigger/L1TMLDemo/plugins/BuildFile.xml new file mode 100644 index 0000000000000000000000000000000000000000..7431fa5b0316d88b776477fb47f8e34022eff6a9 --- /dev/null +++ b/part3/cmssw/src/L1Trigger/L1TMLDemo/plugins/BuildFile.xml @@ -0,0 +1,8 @@ +<use name="FWCore/Framework"/> +<use name="FWCore/ParameterSet"/> +<use name="FWCore/PluginManager"/> +<use name="DataFormats/L1Trigger"/> +<use name="DataFormats/NanoAOD"/> +<use name="hls"/> +<use name="hls4mlEmulatorExtras"/> +<flags EDM_PLUGIN="1"/> \ No newline at end of file diff --git a/part3/cmssw/src/L1Trigger/L1TMLDemo/plugins/l1tDemoMLAnalyzer.cc b/part3/cmssw/src/L1Trigger/L1TMLDemo/plugins/l1tDemoMLAnalyzer.cc new file mode 100644 index 0000000000000000000000000000000000000000..6989315d52c03981be3c43f01b5959bb2e01f3bf --- /dev/null +++ b/part3/cmssw/src/L1Trigger/L1TMLDemo/plugins/l1tDemoMLAnalyzer.cc @@ -0,0 +1,172 @@ +// FWCore includes +#include "FWCore/Framework/interface/global/EDProducer.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/Framework/interface/ESHandle.h" +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" + +// File writing includes +#include "DataFormats/NanoAOD/interface/FlatTable.h" +//#include "TTree.h" +//#include "FWCore/ServiceRegistry/interface/Service.h" +//#include "CommonTools/UtilAlgos/interface/TFileService.h" + +// L1T includes +#include "DataFormats/L1Trigger/interface/Muon.h" +#include "DataFormats/L1Trigger/interface/EGamma.h" +#include "DataFormats/L1Trigger/interface/Tau.h" +#include "DataFormats/L1Trigger/interface/Jet.h" +#include "DataFormats/L1Trigger/interface/EtSum.h" + +// hls & hls4ml includes +#include "ap_fixed.h" +#include "hls4ml/emulator.h" + +#include <iostream> + +class L1TMLDemoProducer : public edm::global::EDProducer<> { +public: + explicit L1TMLDemoProducer(const edm::ParameterSet& cfg); + ~L1TMLDemoProducer(); + +private: + virtual void beginJob() override; + virtual void produce(edm::StreamID id, edm::Event& iEvent, const edm::EventSetup& iSetup) const override; + virtual void endJob() override; + + edm::EDGetToken muToken; + edm::EDGetToken egToken; + edm::EDGetToken tauToken; + edm::EDGetToken jetToken; + edm::EDGetToken sumToken; + + unsigned nMu; + unsigned nEG; + unsigned nTau; + unsigned nJet; + unsigned nNNIn; + + // hls4ml emulator model path + std::string model_so_path; + +}; + +L1TMLDemoProducer::L1TMLDemoProducer(const edm::ParameterSet& cfg){ + // consume + muToken = consumes<l1t::MuonBxCollection>(cfg.getParameter<edm::InputTag>("muToken")); + egToken = consumes<l1t::EGammaBxCollection>(cfg.getParameter<edm::InputTag>("egToken")); + tauToken = consumes<l1t::TauBxCollection>(cfg.getParameter<edm::InputTag>("tauToken")); + jetToken = consumes<l1t::JetBxCollection>(cfg.getParameter<edm::InputTag>("jetToken")); + sumToken = consumes<l1t::EtSumBxCollection>(cfg.getParameter<edm::InputTag>("etSumToken")); + nMu = cfg.getParameter<unsigned>("nMu"); + nEG = cfg.getParameter<unsigned>("nEg"); + nTau = cfg.getParameter<unsigned>("nTau"); + nJet = cfg.getParameter<unsigned>("nJet"); + // total number of inputs to NN + nNNIn = 3 * (1 + nMu + nEG + nTau + nJet); + + // store the path to the .so file + model_so_path = cfg.getParameter<std::string>("model_so_path"); + + // produce + produces<nanoaod::FlatTable>("L1TMLDemo"); + +} + +L1TMLDemoProducer::~L1TMLDemoProducer(){ +} + +void L1TMLDemoProducer::produce(edm::StreamID id, edm::Event& iEvent, const edm::EventSetup& iSetup) const { + using namespace edm; + // get input collections + // BXVector: first index is BX, second index is object + edm::Handle<BXVector<l1t::Muon>> muons; + edm::Handle<BXVector<l1t::EGamma>> egammas; + edm::Handle<BXVector<l1t::Tau>> taus; + edm::Handle<BXVector<l1t::Jet>> jets; + edm::Handle<BXVector<l1t::EtSum>> sums; + iEvent.getByToken(muToken, muons); + iEvent.getByToken(egToken, egammas); + iEvent.getByToken(tauToken, taus); + iEvent.getByToken(jetToken, jets); + iEvent.getByToken(sumToken, sums); + + // The unscaled inputs are hwInts apart from ET that is in GeV with 0.5 GeV LSB + // ap_fixed<14,13> is wide enough for all the ET, pT, eta, phi + ap_fixed<14,13>* X_unscaled = new ap_fixed<14,13>[nNNIn]; + + // fill the inputs + unsigned ix = 0; + // sums first, just find the MET + for(unsigned i = 0; i < sums->size(0); i++){ + if(sums->at(0, i).getType() == l1t::EtSum::EtSumType::kMissingEt){ + X_unscaled[ix++] = (float)(sums->at(0,i).hwPt())/2; + X_unscaled[ix++] = 0; // for eta + X_unscaled[ix++] = sums->at(0,i).hwPhi(); + } + } + // muons next + ix=3 * ( 1 ); + for(unsigned i = 0; i < std::min(nMu, muons->size(0)); i++){ + X_unscaled[ix++] = (float)(muons->at(0, i).hwPt())/2; + X_unscaled[ix++] = muons->at(0, i).hwEta(); + X_unscaled[ix++] = muons->at(0, i).hwPhi(); + } + // egammas next + ix = 3 * ( 1 + nMu ); + for(unsigned i = 0; i < std::min(nEG, egammas->size(0)); i++){ + X_unscaled[ix++] = (float)(egammas->at(0, i).hwPt())/2; + X_unscaled[ix++] = egammas->at(0, i).hwEta(); + X_unscaled[ix++] = egammas->at(0, i).hwPhi(); + } + // taus next + ix = 3 * ( 1 + nMu + nEG ); + for(unsigned i = 0; i < std::min(nTau, taus->size(0)); i++){ + X_unscaled[ix++] = (float)(taus->at(0, i).hwPt())/2; + X_unscaled[ix++] = taus->at(0, i).hwEta(); + X_unscaled[ix++] = taus->at(0, i).hwPhi(); + } + // jets last + ix = 3 * ( 1 + nMu + nEG + nTau ); + for(unsigned i = 0; i < std::min(nEG, jets->size(0)); i++){ + X_unscaled[ix++] = (float)(jets->at(0, i).hwPt())/2; + X_unscaled[ix++] = jets->at(0, i).hwEta(); + X_unscaled[ix++] = jets->at(0, i).hwPhi(); + } + + + ap_fixed<16,7,AP_RND,AP_SAT>* X_scaled = new ap_fixed<16,7,AP_RND,AP_SAT>[nNNIn]; + // scale the inputs + for(unsigned i = 0; i < nNNIn; i++){ + X_scaled[i] = X_unscaled[i]; // placeholder + } + + // load the NN emulator object + hls4mlEmulator::ModelLoader loader(model_so_path); + std::shared_ptr<hls4mlEmulator::Model> model = loader.load_model(); + + ap_fixed<13,2,AP_RND,AP_SAT> y; // output object + + // run the actual inference + model->prepare_input(X_scaled); + model->predict(); + model->read_result(&y); + + // write the result to the output + // note cast from the ap_fixed emulated type to float for convenience + auto out = std::make_unique<nanoaod::FlatTable>(1, "L1TMLDemo", false); + std::vector<float> y_vec; + y_vec.push_back(y); + out->addColumn<float>("y", y_vec, "model prediction"); + iEvent.put(std::move(out), "L1TMLDemo"); + +} + +void L1TMLDemoProducer::beginJob(){ +} + +void L1TMLDemoProducer::endJob(){ +} + +DEFINE_FWK_MODULE(L1TMLDemoProducer); diff --git a/part3/cmssw/src/L1Trigger/L1TMLDemo/test/L1TMLDemo_NanoAOD.root b/part3/cmssw/src/L1Trigger/L1TMLDemo/test/L1TMLDemo_NanoAOD.root new file mode 100644 index 0000000000000000000000000000000000000000..c51965712f6945cce9169ecab07e55f4248c6c72 Binary files /dev/null and b/part3/cmssw/src/L1Trigger/L1TMLDemo/test/L1TMLDemo_NanoAOD.root differ diff --git a/part3/cmssw/src/L1Trigger/L1TMLDemo/test/demoL1TMLNtuple.py b/part3/cmssw/src/L1Trigger/L1TMLDemo/test/demoL1TMLNtuple.py new file mode 100644 index 0000000000000000000000000000000000000000..39abe09ede4fa3de0f0028c24ef153632fdc8413 --- /dev/null +++ b/part3/cmssw/src/L1Trigger/L1TMLDemo/test/demoL1TMLNtuple.py @@ -0,0 +1,50 @@ +# import of standard configurations +import FWCore.ParameterSet.Config as cms + +process = cms.Process("l1tMLDemo") + +process.load('Configuration.StandardSequences.Services_cff') +process.load('FWCore.MessageService.MessageLogger_cfi') +process.load('Configuration.StandardSequences.GeometryRecoDB_cff') +process.load('Configuration.Geometry.GeometryDB_cff') +process.load('Configuration.StandardSequences.MagneticField_38T_cff') +process.load('Configuration.StandardSequences.SimL1Emulator_cff') +process.load('Configuration.StandardSequences.EndOfProcess_cff') +process.load('Configuration.StandardSequences.FrontierConditions_GlobalTag_cff') + +process.maxEvents = cms.untracked.PSet( + input = cms.untracked.int32(-1) +) + +process.source = cms.Source ( + "PoolSource", + fileNames = cms.untracked.vstring('/store/relval/CMSSW_13_3_0_pre3/RelValMinBias_14TeV/GEN-SIM-DIGI-RAW/132X_mcRun3_2023_realistic_v4-v1/2580000/0911bb55-82fb-4a51-bb8f-be79f61b020d.root'), +) + +from Configuration.AlCa.GlobalTag import GlobalTag +process.GlobalTag = GlobalTag(process.GlobalTag, 'auto:startup', '') + +process.l1tDemoMLProducer = cms.EDProducer('L1TMLDemoProducer', + muToken = cms.InputTag("simGmtStage2Digis"), + egToken = cms.InputTag("simCaloStage2Digis"), + tauToken = cms.InputTag("simCaloStage2Digis"), + jetToken = cms.InputTag("simCaloStage2Digis"), + etSumToken = cms.InputTag("simCaloStage2Digis"), + nMu = cms.uint32(2), + nEg = cms.uint32(2), + nTau = cms.uint32(0), + nJet = cms.uint32(4), + model_so_path = cms.string("../data/L1TMLDemo_v1") +) + +process.path = cms.Path( + process.l1tDemoMLProducer +) + +process.outnano = cms.OutputModule("NanoAODOutputModule", + fileName = cms.untracked.string("L1TMLDemo_NanoAOD.root"), + outputCommands = cms.untracked.vstring("drop *", "keep nanoaodFlatTable_*_*_*"), + compressionLevel = cms.untracked.int32(4), + compressionAlgorithm = cms.untracked.string("ZLIB"), +) +process.end = cms.EndPath(process.outnano) \ No newline at end of file diff --git a/part3/cmssw/src/L1Trigger/L1TMLDemo/test/demoL1TMLNtuple.root b/part3/cmssw/src/L1Trigger/L1TMLDemo/test/demoL1TMLNtuple.root new file mode 100644 index 0000000000000000000000000000000000000000..349d83e7446540ff9d4c3e19ec9d33441e5a4a31 Binary files /dev/null and b/part3/cmssw/src/L1Trigger/L1TMLDemo/test/demoL1TMLNtuple.root differ diff --git a/part3/exercise.md b/part3/exercise.md index 7f46fdc1d4faa49a902f49f5e3da0e27b8f98a22..8bea47f54bbc603fee91b38059223c8c95ebf258 100644 --- a/part3/exercise.md +++ b/part3/exercise.md @@ -1,3 +1,6 @@ +In this exercise you will be guided through the steps to create, compile, and run the emulator of the hls4ml model you trained in part 2. The code in these steps should be executed from the command line on `lxplus` after doing `source setup.sh` from this `cms_mlatl1t_tutorial`. + +When developing your own hls4ml NN emulators, you should compile and run your model emulator locally before delivering it to `cms-hls4ml`. ## Prerequisite @@ -8,8 +11,8 @@ You will need the HLS for the model of part 2. Copy the NN-specific part of the hls4ml project to the `cms-hls4ml` repo. We _don't_ copy `ap_types` since we'll reference them from the externals. ```shell -[ ! -d part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/NN ] && mkdir part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/NN -cp -r L1TMLDemo/firmware/{*.h,*.cpp,weights,nnet_utils} part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/NN +[ ! -d $MLATL1T_DIR/part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/NN ] && mkdir $MLATL1T_DIR/part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/NN +cp -r $MLATL1T_DIR/part2/L1TMLDemo_v1/firmware/{*.h,*.cpp,weights,nnet_utils} $MLATL1T_DIR/part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/NN ``` ## 2. @@ -19,7 +22,7 @@ As of `hls4ml` `0.8.1`, when run outside of Vivado HLS, the C++ code loads the w This one liner will replace the `#define` that would cause the weights to be loaded from txt files with one that will load them from the header files when we compile instead. ```shell -find part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/NN \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i 's/#ifndef __SYNTHESIS__/#ifdef __HLS4ML_LOAD_TXT_WEIGHTS__/' +find $MLATL1T_DIR/part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/NN \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i 's/#ifndef __SYNTHESIS__/#ifdef __HLS4ML_LOAD_TXT_WEIGHTS__/' ``` ## 3. @@ -27,7 +30,7 @@ find part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1/NN \( -type d -name .git -prune \) `make` the hls4ml emulator interface shared object ```shell -cd part3/cms-hls4ml/hls4mlEmulatorExtras +cd $MLATL1T_DIR/part3/cms-hls4ml/hls4mlEmulatorExtras make mkdir lib64 mv libemulator_interface.so lib64 @@ -38,10 +41,39 @@ mv libemulator_interface.so lib64 `make` the `L1TMLDemo` model shared object ```shell -cd part3/cms-hls4ml/L1TMLDemo +cd $MLATL1T_DIR/part3/cms-hls4ml/L1TMLDemo make ``` +*Note* you might benefit from adding `-g` to `CXXFLAGS` to compile with debugging while developing. +The Makefile line would change to `CXXFLAGS := -O3 -fPIC -std=$(CPP_STANDARD) -g`. + + ## 5. -CMSSW emulator... \ No newline at end of file +`scram build` compile the CMSSW code + +```shell +cd $CMSSW_BASE/src +scram b -j8 +``` + +## 6. + +Copy the `L1TMLDemo` model shared object to the CMSSW area. + +```shell +mkdir $CMSSW_BASE/src/L1Trigger/L1TMLDemo/data +cp $MLATL1T_DIR/part3/cms-hls4ml/L1TMLDemo/L1TMLDemo_v1.so $CMSSW_BASE/src/L1Trigger/L1TMLDemo/data +``` + +## 7. + +Run the test config! + +```shell +cd $CMSSW_BASE/src/L1Trigger/L1TMLDemo/test +cmsRun demoL1TMLNtuple.py +``` + +*Note* when developing your own models, you may unfortunately run into segmentation violations while developing. The most common reason is that the input and output data type set in the producer mismatch the types used by the model emulator. In this emulator workflow, this causes a runtime error rather than a compile time error. diff --git a/setup.sh b/setup.sh index e7212b96651f9fcf3197f96f2bd73d8531b9b3fe..5936ff9e6019ee8ffe4e2668dfdbec4fc8466fbe 100644 --- a/setup.sh +++ b/setup.sh @@ -3,6 +3,7 @@ CMSSW_VERSION=CMSSW_13_3_0_pre3 SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +export MLATL1T_DIR=$SCRIPT_DIR # create CMSSW area if it doesn't exist, and cmsenv [ ! -d CMSSW_13_3_0_pre3 ] && echo "ML@L1T Setup: cmsrel $CMSSW_VERSION" && \