Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-16 09:21:05

0001 // Author: Dante Niewenhuis, VU Amsterdam 07/2023
0002 // Author: Kristupas Pranckietis, Vilnius University 05/2024
0003 // Author: Nopphakorn Subsa-Ard, King Mongkut's University of Technology Thonburi (KMUTT) (TH) 08/2024
0004 // Author: Vincenzo Eduardo Padulano, CERN 10/2024
0005 // Author: Martin Føll, University of Oslo (UiO) & CERN 01/2026
0006 // Author: Silia Taider, CERN 02/2026
0007 
0008 /*************************************************************************
0009  * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers.               *
0010  * All rights reserved.                                                  *
0011  *                                                                       *
0012  * For the licensing terms see $ROOTSYS/LICENSE.                         *
0013  * For the list of contributors see $ROOTSYS/README/CREDITS.             *
0014  *************************************************************************/
0015 
0016 #ifndef ROOT_INTERNAL_ML_RDATALOADERENGINE
0017 #define ROOT_INTERNAL_ML_RDATALOADERENGINE
0018 
0019 #include <condition_variable>
0020 #include <memory>
0021 #include <mutex>
0022 #include <string>
0023 #include <thread>
0024 #include <vector>
0025 
0026 #include "ROOT/ML/RBatchLoader.hxx"
0027 #include "ROOT/ML/RClusterLoader.hxx"
0028 #include "ROOT/ML/RDatasetLoader.hxx"
0029 #include "ROOT/ML/RFlat2DMatrix.hxx"
0030 #include "ROOT/ML/RFlat2DMatrixOperators.hxx"
0031 #include "ROOT/ML/RSampler.hxx"
0032 #include "ROOT/RDF/InterfaceUtils.hxx"
0033 
0034 // Empty namespace to create a hook for the Pythonization
0035 namespace ROOT::Experimental::ML {
0036 }
0037 
0038 namespace ROOT::Experimental::Internal::ML {
0039 /**
0040  \class ROOT::Experimental::Internal::ML::RDataLoaderEngine
0041 \brief
0042 
0043 In this class, the processes of loading clusters (see RClusterLoader) and creating batches from those clusters (see
0044 RBatchLoader) are combined, allowing batches from the training and validation sets to be loaded directly from a dataset
0045 in an RDataFrame.
0046 */
0047 
0048 template <typename... Args>
0049 class RDataLoaderEngine {
0050 private:
0051    std::vector<std::string> fCols;
0052    std::vector<std::size_t> fVecSizes;
0053    std::size_t fBatchSize;
0054    std::size_t fSetSeed;
0055 
0056    // buffer quantities
0057    std::size_t fBatchesInMemory;
0058    std::size_t fBufferCapacity;
0059    std::size_t fLowWatermark;
0060    std::size_t fHighWatermark;
0061 
0062    std::size_t fTrainingClusterIdx{0};
0063    std::size_t fValidationClusterIdx{0};
0064 
0065    float fTestSize;
0066 
0067    std::unique_ptr<RDatasetLoader<Args...>> fDatasetLoader;
0068    std::unique_ptr<RClusterLoader<Args...>> fClusterLoader;
0069    std::unique_ptr<RBatchLoader> fTrainingBatchLoader;
0070    std::unique_ptr<RBatchLoader> fValidationBatchLoader;
0071    std::unique_ptr<RSampler> fTrainingSampler;
0072    std::unique_ptr<RSampler> fValidationSampler;
0073 
0074    std::unique_ptr<RFlat2DMatrixOperators> fTensorOperators;
0075 
0076    std::vector<ROOT::RDF::RNode> fRdfs;
0077 
0078    std::unique_ptr<std::thread> fLoadingThread;
0079    std::condition_variable fLoadingCondition;
0080    std::mutex fLoadingMutex;
0081 
0082    bool fDropRemainder;
0083    bool fShuffle;
0084    bool fLoadEager;
0085    std::string fSampleType;
0086    float fSampleRatio;
0087    bool fReplacement;
0088 
0089    bool fIsActive{false}; // Whether the loading thread is active
0090 
0091    bool fEpochActive{false};
0092    bool fTrainingEpochActive{false};
0093    bool fValidationEpochActive{false};
0094 
0095    std::size_t fNumTrainingEntries;
0096    std::size_t fNumValidationEntries;
0097 
0098    // flattened buffers for chunks and temporary tensors (rows * cols)
0099    std::vector<RFlat2DMatrix> fTrainingDatasets;
0100    std::vector<RFlat2DMatrix> fValidationDatasets;
0101 
0102    RFlat2DMatrix fTrainingDataset;
0103    RFlat2DMatrix fValidationDataset;
0104 
0105    RFlat2DMatrix fSampledTrainingDataset;
0106    RFlat2DMatrix fSampledValidationDataset;
0107 
0108    std::size_t fTrainingEpochCount{0};
0109    std::size_t fValidationEpochCount{0};
0110 
0111 public:
0112    RDataLoaderEngine(const std::vector<ROOT::RDF::RNode> &rdfs, const std::size_t batchSize,
0113                      const std::size_t batchesInMemory, const std::vector<std::string> &cols,
0114                      const std::vector<std::size_t> &vecSizes = {}, const float vecPadding = 0.0,
0115                      const float testSize = 0.0, bool shuffle = true, bool dropRemainder = true,
0116                      const std::size_t setSeed = 0, bool loadEager = false, std::string sampleType = "",
0117                      float sampleRatio = 1.0, bool replacement = false)
0118       : fRdfs(rdfs),
0119         fCols(cols),
0120         fVecSizes(vecSizes),
0121         fBatchSize(batchSize),
0122         fBatchesInMemory(batchesInMemory),
0123         fTestSize(testSize),
0124         fDropRemainder(dropRemainder),
0125         fSetSeed(setSeed),
0126         fShuffle(shuffle),
0127         fLoadEager(loadEager),
0128         fSampleType(sampleType),
0129         fSampleRatio(sampleRatio),
0130         fReplacement(replacement)
0131    {
0132       fTensorOperators = std::make_unique<RFlat2DMatrixOperators>(fShuffle, fSetSeed);
0133 
0134       if (fLoadEager) {
0135          fDatasetLoader = std::make_unique<RDatasetLoader<Args...>>(fRdfs, fTestSize, fCols, fVecSizes, vecPadding,
0136                                                                     fShuffle, fSetSeed);
0137          fDatasetLoader->SplitDatasets();
0138 
0139          if (fSampleType == "") {
0140             fDatasetLoader->ConcatenateDatasets();
0141 
0142             fTrainingDataset = fDatasetLoader->GetTrainingDataset();
0143             fValidationDataset = fDatasetLoader->GetValidationDataset();
0144 
0145             fNumTrainingEntries = fDatasetLoader->GetNumTrainingEntries();
0146             fNumValidationEntries = fDatasetLoader->GetNumValidationEntries();
0147          }
0148 
0149          else {
0150             fTrainingDatasets = fDatasetLoader->GetTrainingDatasets();
0151             fValidationDatasets = fDatasetLoader->GetValidationDatasets();
0152 
0153             fTrainingSampler = std::make_unique<RSampler>(fTrainingDatasets, fSampleType, fSampleRatio, fReplacement,
0154                                                           fShuffle, fSetSeed);
0155             fValidationSampler = std::make_unique<RSampler>(fValidationDatasets, fSampleType, fSampleRatio,
0156                                                             fReplacement, fShuffle, fSetSeed);
0157 
0158             fNumTrainingEntries = fTrainingSampler->GetNumEntries();
0159             fNumValidationEntries = fValidationSampler->GetNumEntries();
0160          }
0161       }
0162 
0163       else {
0164          // scan cluster boundaries
0165          fClusterLoader = std::make_unique<RClusterLoader<Args...>>(fRdfs, fCols, fVecSizes, vecPadding, fTestSize,
0166                                                                     fShuffle, fSetSeed);
0167 
0168          // derive buffer quantities
0169          fBufferCapacity = fBatchSize * fBatchesInMemory;
0170          fLowWatermark = fBufferCapacity / 2;
0171          fHighWatermark = fBufferCapacity;
0172 
0173          // split cluster list into training and validation
0174          fClusterLoader->SplitDataset();
0175          fNumTrainingEntries = fClusterLoader->GetNumTrainingEntries();
0176          fNumValidationEntries = fClusterLoader->GetNumValidationEntries();
0177       }
0178 
0179       fTrainingBatchLoader = std::make_unique<RBatchLoader>(fBatchSize, fCols, fLoadingMutex, fLoadingCondition,
0180                                                             fVecSizes, fNumTrainingEntries, fDropRemainder);
0181       fValidationBatchLoader = std::make_unique<RBatchLoader>(fBatchSize, fCols, fLoadingMutex, fLoadingCondition,
0182                                                               fVecSizes, fNumValidationEntries, fDropRemainder);
0183    }
0184 
0185    ~RDataLoaderEngine() { DeActivate(); }
0186 
0187    void DeActivate()
0188    {
0189       {
0190          std::lock_guard<std::mutex> lock(fLoadingMutex);
0191          if (!fIsActive)
0192             return;
0193          fIsActive = false;
0194       }
0195 
0196       fLoadingCondition.notify_all();
0197 
0198       if (fLoadingThread) {
0199          if (fLoadingThread->joinable()) {
0200             fLoadingThread->join();
0201          }
0202       }
0203 
0204       fLoadingThread.reset();
0205    }
0206 
0207    /// \brief Activate the loading process by spawning the loading thread.
0208    void Activate()
0209    {
0210       {
0211          std::lock_guard<std::mutex> lock(fLoadingMutex);
0212          if (fIsActive)
0213             return;
0214 
0215          fIsActive = true;
0216       }
0217 
0218       if (fLoadEager) {
0219          return;
0220       }
0221 
0222       fLoadingThread = std::make_unique<std::thread>(&RDataLoaderEngine::LoadData, this);
0223    }
0224 
0225    /// \brief Activate the training epoch by starting the batchloader.
0226    void ActivateTrainingEpoch()
0227    {
0228       {
0229          std::lock_guard<std::mutex> lock(fLoadingMutex);
0230          fTrainingEpochActive = true;
0231          fTrainingClusterIdx = 0;
0232          if (!fLoadEager) {
0233             // Shuffle the cluster indices at the beginning of each epoch
0234             fClusterLoader->ShuffleTrainingClusters(fTrainingEpochCount++);
0235          }
0236       }
0237 
0238       fTrainingBatchLoader->Activate();
0239       fLoadingCondition.notify_all();
0240    }
0241 
0242    void DeActivateTrainingEpoch()
0243    {
0244       {
0245          std::lock_guard<std::mutex> lock(fLoadingMutex);
0246          fTrainingEpochActive = false;
0247       }
0248 
0249       fTrainingBatchLoader->Reset();
0250       fTrainingBatchLoader->DeActivate();
0251       fLoadingCondition.notify_all();
0252    }
0253 
0254    void ActivateValidationEpoch()
0255    {
0256       {
0257          std::lock_guard<std::mutex> lock(fLoadingMutex);
0258          fValidationEpochActive = true;
0259          fValidationClusterIdx = 0;
0260          if (!fLoadEager) {
0261             fClusterLoader->ShuffleValidationClusters(fValidationEpochCount++);
0262          }
0263       }
0264 
0265       fValidationBatchLoader->Activate();
0266       fLoadingCondition.notify_all();
0267    }
0268 
0269    void DeActivateValidationEpoch()
0270    {
0271       {
0272          std::lock_guard<std::mutex> lock(fLoadingMutex);
0273          fValidationEpochActive = false;
0274       }
0275 
0276       fValidationBatchLoader->Reset();
0277       fValidationBatchLoader->DeActivate();
0278       fLoadingCondition.notify_all();
0279    }
0280 
0281    /// \brief Main loop for loading clusters and creating batches.
0282    /// The producer (loading thread) will keep loading clusters and creating batches until the end of the epoch is
0283    /// reached, or the generator is deactivated.
0284    void LoadData()
0285    {
0286       std::unique_lock<std::mutex> lock(fLoadingMutex);
0287 
0288       while (true) {
0289          // Wait until we have work or shutdown
0290          fLoadingCondition.wait(lock, [&] {
0291             return !fIsActive ||
0292                    (fTrainingEpochActive && fTrainingClusterIdx < fClusterLoader->GetNumTrainingClusters()) ||
0293                    (fValidationEpochActive && fValidationClusterIdx < fClusterLoader->GetNumValidationClusters());
0294          });
0295 
0296          if (!fIsActive) {
0297             break;
0298          }
0299 
0300          // Helper: check if validation queue below watermark and needs the producer
0301          auto validationEmpty = [&] {
0302             if (!fValidationEpochActive || fValidationClusterIdx >= fClusterLoader->GetNumValidationClusters())
0303                return false;
0304             if (fValidationBatchLoader->isProducerDone())
0305                return false;
0306             return fValidationBatchLoader->GetNumBatchQueue() < fLowWatermark / fBatchSize;
0307          };
0308 
0309          // -- TRAINING --
0310          if (fTrainingEpochActive) {
0311             const std::size_t numTrainingClusters = fClusterLoader->GetNumTrainingClusters();
0312 
0313             while (true) {
0314                // Stop conditions (shutdown or epoch end)
0315                if (!fIsActive || !fTrainingEpochActive)
0316                   break;
0317 
0318                // No more chunks to load: signal consumers
0319                if (fTrainingClusterIdx >= numTrainingClusters) {
0320                   fTrainingBatchLoader->MarkProducerDone();
0321                   break;
0322                }
0323 
0324                // In the case of training prefetching, we could start requesting data for the next training loop while
0325                // validation is active and might need data. To avoid getting stuck in the training loop, we check if the
0326                // validation queue is below watermark and if so, we break out of the training loop.
0327                if (validationEmpty()) {
0328                   break;
0329                }
0330 
0331                // If queue is not empty, wait until it drains below watermark, or validation needs data, or we are
0332                // deactivated.
0333                if (fTrainingBatchLoader->GetNumBatchQueue() >= fLowWatermark / fBatchSize) {
0334                   fLoadingCondition.wait(lock, [&] {
0335                      return !fIsActive || !fTrainingEpochActive ||
0336                             fTrainingBatchLoader->GetNumBatchQueue() < (fLowWatermark / fBatchSize) ||
0337                             validationEmpty();
0338                   });
0339                   continue;
0340                }
0341 
0342                // Accumulate clusters to load, enough to fill the buffer, or until we run out of clusters
0343                std::vector<RClusterRange> trainClustersToLoad;
0344                auto accumulatedEntries = 0;
0345                const bool discovering = !fClusterLoader->IsSplitDiscovered();
0346                while (fTrainingClusterIdx < numTrainingClusters && accumulatedEntries < fBufferCapacity &&
0347                       (!discovering || trainClustersToLoad.empty())) {
0348                   const auto &cluster = fClusterLoader->GetTrainingClusters()[fTrainingClusterIdx++];
0349                   trainClustersToLoad.push_back(cluster);
0350                   accumulatedEntries += cluster.GetNumEntries();
0351                }
0352 
0353                const bool isLastBuffer = (fTrainingClusterIdx >= numTrainingClusters);
0354 
0355                // Release lock while reading and loading data to allow the consumer to access the queue freely in
0356                // parallel. The loading thread re-acquires the lock in CreateBatches when it needs to push batches to
0357                // the queue.
0358                lock.unlock();
0359                RFlat2DMatrix stagingBuffer(accumulatedEntries, fClusterLoader->GetNumChunkCols());
0360                std::size_t rowOffset = 0;
0361 
0362                for (auto &cluster : trainClustersToLoad) {
0363                   auto loadedEntries = fClusterLoader->LoadTrainingClusterInto(stagingBuffer, cluster.rdfIdx,
0364                                                                                cluster.start, cluster.end, rowOffset);
0365                   if (discovering) {
0366                      // For the first epoch, we might discover that the cluster has fewer entries than expected because
0367                      // of filters
0368                      cluster.SetNumEntries(loadedEntries);
0369                   }
0370                   rowOffset += cluster.GetNumEntries();
0371                }
0372 
0373                if (discovering && fNumTrainingEntries == 0 && fClusterLoader->GetNumTrainingEntries() > 0) {
0374                   fNumTrainingEntries = fClusterLoader->GetNumTrainingEntries();
0375                   fNumValidationEntries = fClusterLoader->GetNumValidationEntries();
0376                   fTrainingBatchLoader->RecalculateBatchCounts(fNumTrainingEntries);
0377                   fValidationBatchLoader->RecalculateBatchCounts(fNumValidationEntries);
0378                }
0379 
0380                if (rowOffset < static_cast<std::size_t>(accumulatedEntries)) {
0381                   stagingBuffer.Resize(rowOffset, stagingBuffer.GetCols());
0382                }
0383 
0384                RFlat2DMatrix shuffledStagingBuffer;
0385                fTensorOperators->ShuffleTensor(shuffledStagingBuffer, stagingBuffer);
0386                fTrainingBatchLoader->CreateBatches(shuffledStagingBuffer, isLastBuffer);
0387 
0388                // Re-acquire the lock before the next iteration to check conditions and update indices
0389                lock.lock();
0390 
0391                if (isLastBuffer && discovering) {
0392                   fClusterLoader->FinaliseSplitDiscovery();
0393                }
0394             }
0395          }
0396 
0397          // -- VALIDATION --
0398          if (fValidationEpochActive) {
0399             const std::size_t numValidationClusters = fClusterLoader->GetNumValidationClusters();
0400 
0401             while (true) {
0402                // Stop conditions (shutdown or epoch end)
0403                if (!fIsActive || !fValidationEpochActive)
0404                   break;
0405 
0406                // No more chunks to load: signal consumers
0407                if (fValidationClusterIdx >= numValidationClusters) {
0408                   fValidationBatchLoader->MarkProducerDone();
0409                   break;
0410                }
0411 
0412                // If queue is not hungry, wait until it drains below watermark, or we are deactivated
0413                if (fValidationBatchLoader->GetNumBatchQueue() >= (fLowWatermark / fBatchSize)) {
0414                   fLoadingCondition.wait(lock, [&] {
0415                      return !fIsActive || !fValidationEpochActive ||
0416                             fValidationBatchLoader->GetNumBatchQueue() < (fLowWatermark / fBatchSize);
0417                   });
0418                   continue;
0419                }
0420 
0421                // Accumulate clusters to load, enough to fill the buffer, or until we run out of clusters
0422                std::vector<RClusterRange> valClustersToLoad;
0423                auto accumulatedEntries = 0;
0424                while (fValidationClusterIdx < numValidationClusters && accumulatedEntries < fBufferCapacity) {
0425                   const auto &cluster = fClusterLoader->GetValidationClusters()[fValidationClusterIdx++];
0426                   valClustersToLoad.push_back(cluster);
0427                   accumulatedEntries += cluster.GetNumEntries();
0428                }
0429 
0430                const bool isLastBuffer = (fValidationClusterIdx >= numValidationClusters);
0431 
0432                lock.unlock();
0433 
0434                RFlat2DMatrix stagingBuffer(accumulatedEntries, fClusterLoader->GetNumChunkCols());
0435                std::size_t rowOffset = 0;
0436 
0437                for (const auto &cluster : valClustersToLoad) {
0438                   fClusterLoader->LoadValidationClusterInto(stagingBuffer, cluster.rdfIdx, cluster.start, cluster.end,
0439                                                             rowOffset);
0440                   rowOffset += cluster.GetNumEntries();
0441                }
0442 
0443                RFlat2DMatrix shuffledStagingBuffer;
0444                fTensorOperators->ShuffleTensor(shuffledStagingBuffer, stagingBuffer);
0445                fValidationBatchLoader->CreateBatches(shuffledStagingBuffer, isLastBuffer);
0446 
0447                lock.lock();
0448             }
0449          }
0450       }
0451    }
0452 
0453    /// \brief Create training batches by first loading a chunk (see RClusterLoader) and split it into batches (see
0454    /// RBatchLoader)
0455    void CreateTrainBatches()
0456    {
0457       fTrainingBatchLoader->Activate();
0458 
0459       if (fLoadEager) {
0460          if (fSampleType == "") {
0461             fTensorOperators->ShuffleTensor(fSampledTrainingDataset, fTrainingDataset);
0462          }
0463 
0464          else {
0465             fTrainingSampler->Sampler(fSampledTrainingDataset);
0466          }
0467 
0468          fTrainingBatchLoader->CreateBatches(fSampledTrainingDataset, true);
0469          fTrainingBatchLoader->MarkProducerDone();
0470       }
0471    }
0472 
0473    /// \brief Creates validation batches by first loading a chunk (see RClusterLoader), and then split it into batches
0474    /// (see RBatchLoader)
0475    void CreateValidationBatches()
0476    {
0477       fValidationBatchLoader->Activate();
0478 
0479       if (fLoadEager) {
0480          if (fSampleType == "") {
0481             fTensorOperators->ShuffleTensor(fSampledValidationDataset, fValidationDataset);
0482          }
0483 
0484          else {
0485             fValidationSampler->Sampler(fSampledValidationDataset);
0486          }
0487 
0488          fValidationBatchLoader->CreateBatches(fSampledValidationDataset, true);
0489          fValidationBatchLoader->MarkProducerDone();
0490       }
0491    }
0492 
0493    /// \brief Loads a training batch from the queue
0494    RFlat2DMatrix GetTrainBatch()
0495    {
0496       // Get next batch if available
0497       return fTrainingBatchLoader->GetBatch();
0498    }
0499 
0500    /// \brief Loads a validation batch from the queue
0501    RFlat2DMatrix GetValidationBatch()
0502    {
0503       // Get next batch if available
0504       return fValidationBatchLoader->GetBatch();
0505    }
0506 
0507    std::size_t NumberOfTrainingBatches() { return fTrainingBatchLoader->GetNumBatches(); }
0508    std::size_t NumberOfValidationBatches() { return fValidationBatchLoader->GetNumBatches(); }
0509 
0510    std::size_t TrainRemainderRows() { return fTrainingBatchLoader->GetNumRemainderRows(); }
0511    std::size_t ValidationRemainderRows() { return fValidationBatchLoader->GetNumRemainderRows(); }
0512 
0513    bool IsActive()
0514    {
0515       std::lock_guard<std::mutex> lock(fLoadingMutex);
0516       return fIsActive;
0517    }
0518 
0519    bool IsTrainingActive()
0520    {
0521       std::lock_guard<std::mutex> lock(fLoadingMutex);
0522       return fTrainingEpochActive;
0523    }
0524 
0525    bool IsValidationActive()
0526    {
0527       std::lock_guard<std::mutex> lock(fLoadingMutex);
0528       return fValidationEpochActive;
0529    }
0530 };
0531 
0532 } // namespace ROOT::Experimental::Internal::ML
0533 
0534 #endif // ROOT_INTERNAL_ML_RDATALOADERENGINE