diff --git a/dunereco/CVN/art/CMakeLists.txt b/dunereco/CVN/art/CMakeLists.txt index 86a9dceb..e19762d9 100644 --- a/dunereco/CVN/art/CMakeLists.txt +++ b/dunereco/CVN/art/CMakeLists.txt @@ -41,6 +41,7 @@ art_make(BASENAME_ONLY HDF5::HDF5 MVAAlg Boost::filesystem + larrecodnn::ImagePatternAlgs_NuSonic_Triton MODULE_LIBRARIES dunereco::CVN_func dunereco::CVN_tf diff --git a/dunereco/CVN/art/CVNEvaluator.fcl b/dunereco/CVN/art/CVNEvaluator.fcl index 4b0cc3c6..6020d5a5 100644 --- a/dunereco/CVN/art/CVNEvaluator.fcl +++ b/dunereco/CVN/art/CVNEvaluator.fcl @@ -28,6 +28,43 @@ standard_tfnethandler: NOutputs: 7 } +standard_tritonclient: +{ + #serverURL: "localhost:8001" + #serverURL: "ailab01.fnal.gov:8001" + serverURL: "triton.fnal.gov:443" + #serverURL: "triton-cluster-svc.ml4phys.com:443" + verbose: false + ssl: true + sslRootCertificates: "" + sslPrivateKey: "" + sslCertificateChain: "" + modelName: "cvn" + modelVersion: "" + timeout: 0 + allowedTries: 1 + outputs: [] +} + +standard_tritonhandler: +{ + ChargeLogScale: false + NImageWires: 500 + NImageTDCs: 500 + ReverseViews: [false,true,false] + + InputNames: [ "view0", "view1", "view2" ] + OutputNames: [ "output_is_antineutrino", + "output_flavour", + "output_interaction", + "output_protons", + "output_pions", + "output_pizeros", + "output_neutrons" ] + + TritonConfig: @local::standard_tritonclient +} + standard_cvnevaluator: { module_type: CVNEvaluator @@ -36,7 +73,9 @@ standard_cvnevaluator: ResultLabel: "cvnresult" #CaffeNetHandler: @local::standard_caffenethandler TFNetHandler: @local::standard_tfnethandler + TritonHandler: @local::standard_tritonhandler CVNType: "Tensorflow" + #CVNType: "Triton" MultiplePMs: false } diff --git a/dunereco/CVN/art/CVNEvaluator_module.cc b/dunereco/CVN/art/CVNEvaluator_module.cc index aa171f84..81ef15c5 100644 --- a/dunereco/CVN/art/CVNEvaluator_module.cc +++ b/dunereco/CVN/art/CVNEvaluator_module.cc @@ -26,6 +26,7 @@ #include "dunereco/CVN/func/PixelMap.h" //#include "dunereco/CVN/art/CaffeNetHandler.h" #include "dunereco/CVN/art/TFNetHandler.h" +#include "dunereco/CVN/art/TritonHandler.h" #include "dunereco/CVN/func/AssignLabels.h" #include "dunereco/CVN/func/InteractionType.h" @@ -53,7 +54,7 @@ namespace cvn { //cvn::CaffeNetHandler fCaffeHandler; cvn::TFNetHandler fTFHandler; - + cvn::TritonHandler fTritonHandler; /// Number of outputs fron neural net //unsigned int fNOutput; @@ -83,6 +84,7 @@ namespace cvn { fCVNType (pset.get ("CVNType")), //fCaffeHandler (pset.get ("CaffeNetHandler")), fTFHandler (pset.get ("TFNetHandler")), + fTritonHandler (pset.get ("TritonHandler")), //fNOutput (fCaffeHandler.NOutput()), fMultiplePMs (pset.get ("MultiplePMs")) { @@ -158,6 +160,7 @@ namespace cvn { } }*/ + if(fCVNType == "TF" || fCVNType == "Tensorflow" || fCVNType == "TensorFlow"){ // If we have a pixel map then use the TF interface to give us a prediction if(pixelmaplist.size() > 0){ @@ -166,6 +169,7 @@ namespace cvn { // cvn::Result can now take a vector of floats and works out the number of outputs resultCol->emplace_back(networkOutput); + /* for(auto const& resaux: (*resultCol)) { @@ -214,9 +218,22 @@ namespace cvn { } } - } - else{ - mf::LogError("CVNEvaluator::produce") << "CVN Type not in the allowed list: Tensorflow" << std::endl; + }else if(fCVNType == "Triton"){ + + if(pixelmaplist.size() > 0){ + std::vector< std::vector > networkOutput = fTritonHandler.Predict(*pixelmaplist[0]); + resultCol->emplace_back(networkOutput); + + if(fMultiplePMs){ + for(unsigned int p = 1; p < pixelmaplist.size(); ++p){ + std::vector< std::vector > output = fTritonHandler.Predict(*pixelmaplist[p]); + resultCol->emplace_back(output); + } + } + } + + }else{ + mf::LogError("CVNEvaluator::produce") << "CVN Type not in the allowed list: Tensorflow,Triton" << std::endl; mf::LogError("CVNEvaluator::produce") << "Exiting without processing events" << std::endl; return; } @@ -323,10 +340,3 @@ namespace cvn { DEFINE_ART_MODULE(cvn::CVNEvaluator) } // end namespace cvn //////////////////////////////////////////////////////////////////////// - - - - - - - diff --git a/dunereco/CVN/art/TritonHandler.cxx b/dunereco/CVN/art/TritonHandler.cxx new file mode 100644 index 00000000..2057bc96 --- /dev/null +++ b/dunereco/CVN/art/TritonHandler.cxx @@ -0,0 +1,236 @@ +//////////////////////////////////////////////////////////////////////// +/// \file TritonHandler.cxx +/// \brief TritonHandler for CVN +//////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include "canvas/Utilities/Exception.h" +#include "messagefacility/MessageLogger/MessageLogger.h" + +#include "dunereco/CVN/art/TritonHandler.h" +#include "dunereco/CVN/func/CVNImageUtils.h" + + +/* +namespace +{ + // Order-sensitive, bit-exact hash over the raw float data (FNV-1a). + // // Any difference in content OR ordering will change this value. + uint64_t ChargeVecHash(const std::vector& v) + { + uint64_t hash = 14695981039346656037ULL; // FNV offset basis + const uint64_t prime = 1099511628211ULL; + const unsigned char* bytes = reinterpret_cast(v.data()); + size_t nbytes = v.size() * sizeof(float); + for (size_t i = 0; i < nbytes; ++i) + { + hash ^= bytes[i]; + hash *= prime; + } + return hash; + } + + void PrintChargeChecksum(const char* label, const std::vector& v) + { + double sum = 0.0; + for (float x : v) sum += x; + + std::cout << "[ChargeChecksum] " << label + << " size=" << v.size() + << " sum=" << std::setprecision(17) << sum + << " hash=0x" << std::hex << ChargeVecHash(v) << std::dec + << std::endl; + } +} +*/ + +namespace cvn +{ + + TritonHandler::TritonHandler(const fhicl::ParameterSet& pset): + fUseLogChargeScale(pset.get("ChargeLogScale")), + fImageWires(pset.get("NImageWires")), + fImageTDCs(pset.get("NImageTDCs")), + fNViews(3), + fReverseViews(pset.get>("ReverseViews")), + fInputNames(pset.get>( + "InputNames", {"view0", "view1", "view2"})), + fOutputNames(pset.get>( + "OutputNames", {"output_is_antineutrino", + "output_flavour", + "output_interaction", + "output_protons", + "output_pions", + "output_pizeros", + "output_neutrons"})) + { + mf::LogInfo("TritonHandler") << "Loading Triton client" << std::endl; + + std::cout << "Loading Triton client: "; + fTritonClient = std::make_unique(pset.get("TritonConfig")); + if (!fTritonClient){ + art::Exception(art::errors::Unknown) << "Triton client not created correctly"; + } + } + + // Check the network outputs + bool check_Triton(const std::vector< std::vector< float > > & outputs) + { + if (outputs.size() == 1) return true; + size_t aux = 0; + for (size_t o = 0; o < outputs.size(); ++o) + { + size_t aux2 = 0; + + for (size_t i = 0; i < outputs[o].size(); ++i) + if (outputs[o][i] == 0.0 || outputs[o][i] == 1.0) + aux2++; + if (aux2 == outputs[o].size()) aux++; + } + return aux == outputs.size() ? false : true; + } + + // Fill outputs with value -3 + void fillEmpty_Triton(std::vector< std::vector< float > > & outputs) + { + std::cout << "Inside fillEmpty_Triton: "; + for (size_t o = 0; o < outputs.size(); ++o) + { + for (size_t i = 0; i < outputs[o].size(); ++i) + outputs[o][i] = -3.0; + } + return; + } + + + void TritonHandler::SplitViews(const ImageVectorF& fullImage, + std::vector>& viewData) const + { + viewData.clear(); + viewData.resize(fNViews); + + const size_t nWires = fullImage.size(); + for (auto& vd : viewData) vd.reserve(nWires * (nWires ? fullImage[0].size() : 0)); + + for (size_t w = 0; w < nWires; ++w) + { + const size_t nTdcs = fullImage[w].size(); + for (size_t t = 0; t < nTdcs; ++t) + { + const auto& channelVec = fullImage[w][t]; // size fNViews (3): {v0, v1, v2} + for (unsigned v = 0; v < fNViews; ++v) + viewData[v].push_back(channelVec[v]); + } + } + } + + + std::vector< std::vector > TritonHandler::Predict(const PixelMap& pm) + { + ///==== + + std::cout << "Inside Predict: "; + CVNImageUtils imageUtils(fImageWires,fImageTDCs, fNViews); + // Configure the image utility + imageUtils.SetViewReversal(fReverseViews); + imageUtils.SetImageSize(fImageWires,fImageTDCs,fNViews); + imageUtils.SetLogScale(fUseLogChargeScale); + imageUtils.SetPixelMapSize(pm.NWire(), pm.NTdc()); + + + // --- DEBUG: checksum the raw PixelMap charge vectors --- + //PrintChargeChecksum("view0 (fPEX)", pm.fPEX); + //PrintChargeChecksum("view1 (fPEY)", pm.fPEY); + //PrintChargeChecksum("view2 (fPEZ)", pm.fPEZ); + // -------------------------------------------------------- + + + + ImageVectorF thisImage; + imageUtils.ConvertPixelMapToImageVectorF(pm,thisImage); + + // Split the [view][wire][tdc] image into one flat buffer per view + std::vector> viewData; + SplitViews(thisImage, viewData); + + bool status = false; + int counter = 0; + std::vector< std::vector > cvnResults; // shape(#outputs, output_size) + + do{ // do until it gets a correct result + fTritonClient->reset(); + + // model has max_batch_size: 0, fixed shape [1,500,500,1] per view, + // so no setBatchSize()/setShape() calls are needed here + for (unsigned v = 0; v < fNViews; ++v) + { + //std::cout << "[SplitViewsCheck] view " << v << " size=" << viewData[v].size() << std::endl; + auto& input = fTritonClient->input().at(fInputNames[v]); + auto inputData = std::make_shared>(); + inputData->push_back(viewData[v]); + input.toServer(inputData); + } + + fTritonClient->dispatch(); + + cvnResults.clear(); + for (auto const& outName : fOutputNames) + { + auto const& output = fTritonClient->output().at(outName); + lartriton::TritonOutput outputData = output.fromServer(); + cvnResults.emplace_back(outputData[0].begin(), outputData[0].end()); + } + + status = check_Triton(cvnResults); + counter++; + if(counter==10){ + std::cout << "Error, CVN never outputing a correct result. Filling result with zeros."; + std::cout << std::endl; + fillEmpty_Triton(cvnResults); + break; + } + }while(status == false); + + std::cout << "Classifier summary: "; + std::cout << "Triton is working: "; + std::cout << std::endl; + int output_index = 0; + for(auto const & output : cvnResults) + { + std::cout << "Output " << output_index++ << ": "; + for(auto const v : output) + std::cout << v << ", "; + std::cout << std::endl; + } + std::cout << std::endl; + + return cvnResults; + } + + int TritonHandler::NOutput() const + { + return static_cast(fOutputNames.size()); + } + + int TritonHandler::NFeatures() const + { + return static_cast(fNViews); + } + + // The output_flavour head is already a 4-element vector + // [numu, nue, nutau, NC]-style per config.pbtxt (dims: [1,4]) + std::vector TritonHandler::PredictFlavour(const PixelMap& pm){ + std::cout << "Inside Flavour: "; + + std::vector> fullResults = this->Predict(pm); + return fullResults.at(1); // output_flavour + + } + +} + diff --git a/dunereco/CVN/art/TritonHandler.h b/dunereco/CVN/art/TritonHandler.h new file mode 100644 index 00000000..87a19e96 --- /dev/null +++ b/dunereco/CVN/art/TritonHandler.h @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////////////// +/// \file TritonHandler.h +/// \brief TritonHandler for CVN +//////////////////////////////////////////////////////////////////////// +#ifndef CVN_TRITONHANDLER_H +#define CVN_TRITONHANDLER_H + +#include +#include +#include + +#include "fhiclcpp/ParameterSet.h" + +#include "dunereco/CVN/func/PixelMap.h" +#include "dunereco/CVN/func/InteractionType.h" +#include "dunereco/CVN/func/CVNImageUtils.h" + +#include "larrecodnn/ImagePatternAlgs/NuSonic/Triton/TritonClient.h" + +namespace cvn +{ + /// Wrapper for a Triton inference client which handles construction and prediction + class TritonHandler + { + public: + /// Constructor which takes a pset with TritonConfig and image/view fields + TritonHandler(const fhicl::ParameterSet& pset); + + /// Number of outputs in neural net (i.e. number of output heads) + int NOutput() const; + + /// Number of input views/features fed to the neural net + int NFeatures() const; + + /// Return prediction arrays for PixelMap, one vector per output head: + /// [0] output_is_antineutrino (size 1) + /// [1] output_flavour (size 4) + /// [2] output_interaction (size 4) + /// [3] output_protons (size 4) + /// [4] output_pions (size 4) + /// [5] output_pizeros (size 4) + /// [6] output_neutrons (size 4) + std::vector> Predict(const PixelMap& pm); + + /// Return four element vector with summed numu, nue, nutau and NC elements + std::vector PredictFlavour(const PixelMap& pm); + + private: + /// Split a flattened multi-view image into per-view buffers for Triton inputs + void SplitViews(const ImageVectorF& fullImage, + std::vector>& viewData) const; + + std::unique_ptr fTritonClient; ///< Triton inference client + + bool fUseLogChargeScale; ///< Is the charge using a log scale? + unsigned int fImageWires; ///< Number of wires for the network to classify + unsigned int fImageTDCs; ///< Number of tdcs for the network to classify + unsigned int fNViews; ///< Number of views (input tensors) + std::vector fReverseViews; ///< Do we need to reverse any views? + + std::vector fInputNames; ///< Names of the Triton input tensors (per view) + std::vector fOutputNames; ///< Names of the Triton output tensors (per head) + }; +} +#endif // CVN_TRITONHANDLER_H