|
4 | 4 | #include "FWCore/Framework/interface/MakerMacros.h"
|
5 | 5 |
|
6 | 6 | #include "DataFormats/ParticleFlowCandidate/interface/PFCandidate.h"
|
7 |
| -#include "PhysicsTools/TensorFlow/interface/TensorFlow.h" |
| 7 | +#include "PhysicsTools/ONNXRuntime/interface/ONNXRuntime.h" |
8 | 8 | #include "RecoParticleFlow/PFProducer/interface/MLPFModel.h"
|
9 | 9 |
|
10 |
| -struct MLPFCache { |
11 |
| - const tensorflow::GraphDef* graph_def; |
12 |
| -}; |
| 10 | +#include "DataFormats/ParticleFlowReco/interface/PFBlockElementTrack.h" |
| 11 | + |
| 12 | +using namespace cms::Ort; |
| 13 | + |
| 14 | +//use this to switch on detailed print statements in MLPF |
| 15 | +//#define MLPF_DEBUG |
13 | 16 |
|
14 |
| -class MLPFProducer : public edm::stream::EDProducer<edm::GlobalCache<MLPFCache> > { |
| 17 | +class MLPFProducer : public edm::stream::EDProducer<edm::GlobalCache<ONNXRuntime>> { |
15 | 18 | public:
|
16 |
| - explicit MLPFProducer(const edm::ParameterSet&, const MLPFCache*); |
| 19 | + explicit MLPFProducer(const edm::ParameterSet&, const ONNXRuntime*); |
| 20 | + |
17 | 21 | void produce(edm::Event& event, const edm::EventSetup& setup) override;
|
18 | 22 | static void fillDescriptions(edm::ConfigurationDescriptions& descriptions);
|
19 | 23 |
|
20 | 24 | // static methods for handling the global cache
|
21 |
| - static std::unique_ptr<MLPFCache> initializeGlobalCache(const edm::ParameterSet&); |
22 |
| - static void globalEndJob(MLPFCache*); |
| 25 | + static std::unique_ptr<ONNXRuntime> initializeGlobalCache(const edm::ParameterSet&); |
| 26 | + static void globalEndJob(const ONNXRuntime*); |
23 | 27 |
|
24 | 28 | private:
|
25 | 29 | const edm::EDPutTokenT<reco::PFCandidateCollection> pfCandidatesPutToken_;
|
26 | 30 | const edm::EDGetTokenT<reco::PFBlockCollection> inputTagBlocks_;
|
27 |
| - const std::string model_path_; |
28 |
| - tensorflow::Session* session_; |
29 | 31 | };
|
30 | 32 |
|
31 |
| -MLPFProducer::MLPFProducer(const edm::ParameterSet& cfg, const MLPFCache* cache) |
| 33 | +MLPFProducer::MLPFProducer(const edm::ParameterSet& cfg, const ONNXRuntime* cache) |
32 | 34 | : pfCandidatesPutToken_{produces<reco::PFCandidateCollection>()},
|
33 |
| - inputTagBlocks_(consumes<reco::PFBlockCollection>(cfg.getParameter<edm::InputTag>("src"))), |
34 |
| - model_path_(cfg.getParameter<std::string>("model_path")) { |
35 |
| - session_ = tensorflow::createSession(cache->graph_def); |
36 |
| -} |
| 35 | + inputTagBlocks_(consumes<reco::PFBlockCollection>(cfg.getParameter<edm::InputTag>("src"))) {} |
37 | 36 |
|
38 | 37 | void MLPFProducer::produce(edm::Event& event, const edm::EventSetup& setup) {
|
39 | 38 | using namespace reco::mlpf;
|
40 | 39 |
|
41 | 40 | const auto& blocks = event.get(inputTagBlocks_);
|
42 | 41 | const auto& all_elements = getPFElements(blocks);
|
43 | 42 |
|
44 |
| - const long long int num_elements_total = all_elements.size(); |
| 43 | + std::vector<const reco::PFBlockElement*> selected_elements; |
| 44 | + unsigned int num_elements_total = 0; |
| 45 | + for (const auto* pelem : all_elements) { |
| 46 | + if (pelem->type() == reco::PFBlockElement::PS1 || pelem->type() == reco::PFBlockElement::PS2) { |
| 47 | + continue; |
| 48 | + } |
| 49 | + num_elements_total += 1; |
| 50 | + selected_elements.push_back(pelem); |
| 51 | + } |
| 52 | + const auto tensor_size = LSH_BIN_SIZE * std::max(2u, (num_elements_total / LSH_BIN_SIZE + 1)); |
45 | 53 |
|
| 54 | +#ifdef MLPF_DEBUG |
| 55 | + assert(num_elements_total < NUM_MAX_ELEMENTS_BATCH); |
46 | 56 | //tensor size must be a multiple of the bin size and larger than the number of elements
|
47 |
| - const auto tensor_size = LSH_BIN_SIZE * (num_elements_total / LSH_BIN_SIZE + 1); |
48 | 57 | assert(tensor_size <= NUM_MAX_ELEMENTS_BATCH);
|
| 58 | + assert(tensor_size % LSH_BIN_SIZE == 0); |
| 59 | +#endif |
49 | 60 |
|
50 |
| - //Create the input tensor |
51 |
| - tensorflow::TensorShape shape({BATCH_SIZE, tensor_size, NUM_ELEMENT_FEATURES}); |
52 |
| - tensorflow::Tensor input(tensorflow::DT_FLOAT, shape); |
53 |
| - input.flat<float>().setZero(); |
| 61 | +#ifdef MLPF_DEBUG |
| 62 | + std::cout << "tensor_size=" << tensor_size << std::endl; |
| 63 | +#endif |
54 | 64 |
|
55 |
| - //Fill the input tensor |
| 65 | + //Fill the input tensor (batch, elems, features) = (1, tensor_size, NUM_ELEMENT_FEATURES) |
| 66 | + std::vector<std::vector<float>> inputs(1, std::vector<float>(NUM_ELEMENT_FEATURES * tensor_size, 0.0)); |
56 | 67 | unsigned int ielem = 0;
|
57 |
| - for (const auto* pelem : all_elements) { |
| 68 | + for (const auto* pelem : selected_elements) { |
| 69 | + if (ielem > tensor_size) { |
| 70 | + continue; |
| 71 | + } |
| 72 | + |
58 | 73 | const auto& elem = *pelem;
|
59 | 74 |
|
60 | 75 | //prepare the input array from the PFElement
|
61 | 76 | const auto& props = getElementProperties(elem);
|
62 | 77 |
|
63 | 78 | //copy features to the input array
|
64 | 79 | for (unsigned int iprop = 0; iprop < NUM_ELEMENT_FEATURES; iprop++) {
|
65 |
| - input.tensor<float, 3>()(0, ielem, iprop) = normalize(props[iprop]); |
| 80 | + inputs[0][ielem * NUM_ELEMENT_FEATURES + iprop] = normalize(props[iprop]); |
66 | 81 | }
|
67 | 82 | ielem += 1;
|
68 | 83 | }
|
69 | 84 |
|
70 |
| - //TF model input and output tensor names |
71 |
| - const tensorflow::NamedTensorList input_list = {{"x:0", input}}; |
72 |
| - const std::vector<std::string> output_names = {"Identity:0"}; |
73 |
| - |
74 |
| - //Prepare the output tensor |
75 |
| - std::vector<tensorflow::Tensor> outputs; |
76 |
| - |
77 | 85 | //run the GNN inference, given the inputs and the output.
|
78 |
| - //Note that the GNN enables information transfer between the input PFElements, |
79 |
| - //such that the output ML-PFCandidates are in general combinations of the input PFElements, in the form of |
80 |
| - //y_out = Adj.x_in, where x_in is input matrix (num_elem, NUM_ELEMENT_FEATURES), y_out is the output matrix (num_elem, NUM_OUTPUT_FEATURES) |
81 |
| - //and Adj is an adjacency matrix between the elements that is constructed on the fly during model inference. |
82 |
| - tensorflow::run(session_, input_list, output_names, &outputs); |
83 |
| - |
84 |
| - //process the output tensor to ML-PFCandidates. |
85 |
| - //The output can contain up to num_elem particles, with predicted PDGID=0 corresponding to no particles predicted. |
86 |
| - const auto out_arr = outputs[0].tensor<float, 3>(); |
| 86 | + const auto& outputs = globalCache()->run({"x:0"}, inputs, {{1, tensor_size, NUM_ELEMENT_FEATURES}}); |
| 87 | + const auto& output = outputs[0]; |
| 88 | +#ifdef MLPF_DEBUG |
| 89 | + assert(output.size() == tensor_size * NUM_OUTPUT_FEATURES); |
| 90 | +#endif |
87 | 91 |
|
88 | 92 | std::vector<reco::PFCandidate> pOutputCandidateCollection;
|
89 |
| - for (unsigned int ielem = 0; ielem < all_elements.size(); ielem++) { |
90 |
| - //get the coefficients in the output corresponding to the class probabilities (raw logits) |
91 |
| - std::vector<float> pred_id_logits; |
92 |
| - for (unsigned int idx_id = 0; idx_id <= NUM_CLASS; idx_id++) { |
93 |
| - pred_id_logits.push_back(out_arr(0, ielem, idx_id)); |
| 93 | + for (size_t ielem = 0; ielem < num_elements_total; ielem++) { |
| 94 | + std::vector<float> pred_id_probas(IDX_CLASS + 1, 0.0); |
| 95 | + const reco::PFBlockElement* elem = selected_elements[ielem]; |
| 96 | + |
| 97 | + for (unsigned int idx_id = 0; idx_id <= IDX_CLASS; idx_id++) { |
| 98 | + auto pred_proba = output[ielem * NUM_OUTPUT_FEATURES + idx_id]; |
| 99 | +#ifdef MLPF_DEBUG |
| 100 | + assert(!std::isnan(pred_proba)); |
| 101 | +#endif |
| 102 | + pred_id_probas[idx_id] = pred_proba; |
94 | 103 | }
|
95 | 104 |
|
| 105 | + auto imax = argMax(pred_id_probas); |
| 106 | + |
96 | 107 | //get the most probable class PDGID
|
97 |
| - int pred_pid = pdgid_encoding[argMax(pred_id_logits)]; |
| 108 | + int pred_pid = pdgid_encoding[imax]; |
98 | 109 |
|
99 |
| - //get the predicted momentum components |
100 |
| - float pred_eta = out_arr(0, ielem, IDX_ETA); |
101 |
| - float pred_phi = out_arr(0, ielem, IDX_PHI); |
102 |
| - float pred_charge = out_arr(0, ielem, IDX_CHARGE); |
103 |
| - float pred_e = out_arr(0, ielem, IDX_ENERGY); |
| 110 | +#ifdef MLPF_DEBUG |
| 111 | + std::cout << "ielem=" << ielem << " inputs:"; |
| 112 | + for (unsigned int iprop = 0; iprop < NUM_ELEMENT_FEATURES; iprop++) { |
| 113 | + std::cout << iprop << "=" << inputs[0][ielem * NUM_ELEMENT_FEATURES + iprop] << " "; |
| 114 | + } |
| 115 | + std::cout << std::endl; |
| 116 | + std::cout << "ielem=" << ielem << " pred: pid=" << pred_pid << std::endl; |
| 117 | +#endif |
104 | 118 |
|
105 | 119 | //a particle was predicted for this PFElement, otherwise it was a spectator
|
106 | 120 | if (pred_pid != 0) {
|
107 |
| - auto cand = makeCandidate(pred_pid, pred_charge, pred_e, pred_eta, pred_phi); |
108 |
| - setCandidateRefs(cand, all_elements, ielem); |
| 121 | + //muons and charged hadrons should only come from tracks, otherwise we won't have track references to pass downstream |
| 122 | + if (((pred_pid == 13) || (pred_pid == 211)) && elem->type() != reco::PFBlockElement::TRACK) { |
| 123 | + pred_pid = 130; |
| 124 | + } |
| 125 | + |
| 126 | + if (elem->type() == reco::PFBlockElement::TRACK) { |
| 127 | + const auto* eltTrack = dynamic_cast<const reco::PFBlockElementTrack*>(elem); |
| 128 | + |
| 129 | + //a track with no muon ref should not produce a muon candidate, instead we interpret it as a charged hadron |
| 130 | + if (pred_pid == 13 && eltTrack->muonRef().isNull()) { |
| 131 | + pred_pid = 211; |
| 132 | + } |
| 133 | + |
| 134 | + //tracks from displaced vertices need reference debugging downstream as well, so we just treat them as neutrals for the moment |
| 135 | + if ((pred_pid == 211) && (eltTrack->isLinkedToDisplacedVertex())) { |
| 136 | + pred_pid = 130; |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + //get the predicted momentum components |
| 141 | + float pred_pt = output[ielem * NUM_OUTPUT_FEATURES + IDX_PT]; |
| 142 | + float pred_eta = output[ielem * NUM_OUTPUT_FEATURES + IDX_ETA]; |
| 143 | + float pred_sin_phi = output[ielem * NUM_OUTPUT_FEATURES + IDX_SIN_PHI]; |
| 144 | + float pred_cos_phi = output[ielem * NUM_OUTPUT_FEATURES + IDX_COS_PHI]; |
| 145 | + float pred_e = output[ielem * NUM_OUTPUT_FEATURES + IDX_ENERGY]; |
| 146 | + float pred_charge = output[ielem * NUM_OUTPUT_FEATURES + IDX_CHARGE]; |
| 147 | + |
| 148 | + auto cand = makeCandidate(pred_pid, pred_charge, pred_pt, pred_eta, pred_sin_phi, pred_cos_phi, pred_e); |
| 149 | + setCandidateRefs(cand, selected_elements, ielem); |
109 | 150 | pOutputCandidateCollection.push_back(cand);
|
| 151 | + |
| 152 | +#ifdef MLPF_DEBUG |
| 153 | + std::cout << "ielem=" << ielem << " cand: pid=" << cand.pdgId() << " E=" << cand.energy() << " pt=" << cand.pt() |
| 154 | + << " eta=" << cand.eta() << " phi=" << cand.phi() << " charge=" << cand.charge() << std::endl; |
| 155 | +#endif |
110 | 156 | }
|
111 | 157 | } //loop over PFElements
|
112 | 158 |
|
113 | 159 | event.emplace(pfCandidatesPutToken_, pOutputCandidateCollection);
|
114 | 160 | }
|
115 | 161 |
|
116 |
| -std::unique_ptr<MLPFCache> MLPFProducer::initializeGlobalCache(const edm::ParameterSet& params) { |
117 |
| - // this method is supposed to create, initialize and return a MLPFCache instance |
118 |
| - std::unique_ptr<MLPFCache> cache = std::make_unique<MLPFCache>(); |
119 |
| - |
120 |
| - //load the frozen TF graph of the GNN model |
121 |
| - std::string path = params.getParameter<std::string>("model_path"); |
122 |
| - auto fullPath = edm::FileInPath(path).fullPath(); |
123 |
| - LogDebug("MLPFProducer") << "Initializing MLPF model from " << fullPath; |
124 |
| - |
125 |
| - cache->graph_def = tensorflow::loadGraphDef(fullPath); |
126 |
| - |
127 |
| - return cache; |
| 162 | +std::unique_ptr<ONNXRuntime> MLPFProducer::initializeGlobalCache(const edm::ParameterSet& params) { |
| 163 | + return std::make_unique<ONNXRuntime>(params.getParameter<edm::FileInPath>("model_path").fullPath()); |
128 | 164 | }
|
129 | 165 |
|
130 |
| -void MLPFProducer::globalEndJob(MLPFCache* cache) { delete cache->graph_def; } |
| 166 | +void MLPFProducer::globalEndJob(const ONNXRuntime* cache) {} |
131 | 167 |
|
132 | 168 | void MLPFProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {
|
133 | 169 | edm::ParameterSetDescription desc;
|
134 | 170 | desc.add<edm::InputTag>("src", edm::InputTag("particleFlowBlock"));
|
135 |
| - desc.add<std::string>("model_path", "RecoParticleFlow/PFProducer/data/mlpf/mlpf_2020_11_04.pb"); |
| 171 | + desc.add<edm::FileInPath>( |
| 172 | + "model_path", |
| 173 | + edm::FileInPath( |
| 174 | + "RecoParticleFlow/PFProducer/data/mlpf/" |
| 175 | + "mlpf_2021_11_16__no_einsum__all_data_cms-best-of-asha-scikit_20211026_042043_178263.workergpu010.onnx")); |
136 | 176 | descriptions.addWithDefaultLabel(desc);
|
137 | 177 | }
|
138 | 178 |
|
|
0 commit comments