Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RDataLoaderEngine.cxx
Go to the documentation of this file.
2
3#include "RBatchLoader.hxx"
4#include "RBatchSink.hxx"
5#include "RClusterLoader.hxx"
6#include "RDatasetLoader.hxx"
8#include "RSampler.hxx"
9
10#include <algorithm>
11
13
14namespace {
15/// \brief Merge the clusters that follow each other in the same dataframe
16std::vector<RClusterRange> MergeConsecutiveClusters(const std::vector<RClusterRange> &clusters)
17{
18 std::vector<RClusterRange> merged;
19 for (const auto &cluster : clusters) {
20 if (!merged.empty() && merged.back().rdfIdx == cluster.rdfIdx && merged.back().end == cluster.start) {
21 merged.back().end = cluster.end;
22 merged.back().SetNumEntries(merged.back().GetNumEntries() + cluster.GetNumEntries());
23 } else {
24 merged.push_back(cluster);
25 }
26 }
27 return merged;
28}
29} // namespace
30
31/// \brief Describe how the loader's columns map onto a batch-tensor row.
32std::vector<RColumnLayout> RDataLoaderEngine::MakeColumnLayout()
33{
34 std::vector<RColumnLayout> layout;
35 layout.reserve(fCols.size());
36
37 std::size_t vecIdx = 0;
38 std::size_t offset = 0;
39 for (const auto &col : fCols) {
40 const bool isVector = fRdfs[0].GetColumnType(col).find("RVec") != std::string::npos;
41 const std::size_t width = isVector ? fVecSizes[vecIdx++] : 1;
42 layout.push_back({col, offset, width, isVector});
43 offset += width;
44 }
45
46 return layout;
47}
48
50 : fEngine(engine), fIsTraining(isTraining)
51{
52 // Same order as the pythonization's epoch context managers
54 if (fIsTraining) {
57 } else {
60 }
61}
62
64{
65 if (fIsTraining)
66 fEngine.DeActivateTrainingEpoch();
67 else
68 fEngine.DeActivateValidationEpoch();
69}
70
71RDataLoaderEngine::RDataLoaderEngine(const std::vector<ROOT::RDF::RNode> &rdfs, const std::size_t batchSize,
72 const std::size_t batchesInMemory, const std::vector<std::string> &cols,
73 const std::vector<std::size_t> &vecSizes, const float vecPadding,
74 const float testSize, bool shuffle, bool dropRemainder, const std::size_t setSeed,
75 bool loadEager, std::string sampleType, float sampleRatio, bool replacement)
76 : fCols(cols),
77 fVecSizes(vecSizes),
78 fBatchSize(batchSize),
79 fSetSeed(setSeed),
80 fTestSize(testSize),
81 fRdfs(rdfs),
82 fDropRemainder(dropRemainder),
83 fShuffle(shuffle),
84 fLoadEager(loadEager),
85 fSampleType(sampleType),
86 fSampleRatio(sampleRatio),
87 fReplacement(replacement)
88{
89 fTensorOperators = std::make_unique<RFlat2DMatrixOperators>(fShuffle, fSetSeed);
90
91 // describe the columns before the eager path releases the dataframes
93
94 if (fLoadEager) {
96 std::make_unique<RDatasetLoader>(fRdfs, fTestSize, fCols, fVecSizes, vecPadding, fShuffle, fSetSeed);
97 fDatasetLoader->SplitDatasets(fSampleType == "");
98 fDataset = fDatasetLoader->ReleaseDataset();
99 fTrainingRows = fDatasetLoader->ReleaseTrainingRows();
100 fValidationRows = fDatasetLoader->ReleaseValidationRows();
101
102 if (fSampleType == "") {
103 fNumTrainingEntries = fDatasetLoader->GetNumTrainingEntries();
104 fNumValidationEntries = fDatasetLoader->GetNumValidationEntries();
105 }
106
107 else {
112
113 fNumTrainingEntries = fTrainingSampler->GetNumEntries();
114 fNumValidationEntries = fValidationSampler->GetNumEntries();
115 }
116
117 // the dataset is in memory now, release the objects used to create it
118 fDatasetLoader.reset();
119 fRdfs.clear();
120 }
121
122 else {
123 // scan cluster boundaries
125 std::make_unique<RClusterLoader>(fRdfs, fCols, fVecSizes, vecPadding, fTestSize, fShuffle, fSetSeed);
126
127 // derive buffer quantities
128 fBufferCapacity = fBatchSize * batchesInMemory;
129 // at least one batch, otherwise the refill threshold rounds down to 0 and nothing is ever loaded
131
132 // split cluster list into training and validation
133 fClusterLoader->SplitDataset();
134 fNumTrainingEntries = fClusterLoader->GetNumTrainingEntries();
135 fNumValidationEntries = fClusterLoader->GetNumValidationEntries();
136 }
137
140 fValidationBatchLoader = std::make_unique<RBatchLoader>(fBatchSize, fCols, fLoadingMutex, fLoadingCondition,
142}
143
148
150{
151 {
152 std::lock_guard<std::mutex> lock(fLoadingMutex);
153 if (!fIsActive)
154 return;
155 fIsActive = false;
156 }
157
158 fLoadingCondition.notify_all();
159
160 if (fLoadingThread) {
161 if (fLoadingThread->joinable()) {
162 fLoadingThread->join();
163 }
164 }
165
166 fLoadingThread.reset();
167}
168
169/// \brief Activate the loading process by spawning the loading thread.
171{
172 {
173 std::lock_guard<std::mutex> lock(fLoadingMutex);
174 if (fIsActive)
175 return;
176
177 fIsActive = true;
178 }
179
180 if (fLoadEager) {
181 return;
182 }
183
184 fLoadingThread = std::make_unique<std::thread>(&RDataLoaderEngine::LoadData, this);
185}
186
187/// \brief Materialize one train/test split to disk by draining a full epoch through the normal batch
188/// pipeline and Fill() each batch into \p filename instead of yielding it.
189///
190/// Filters, shuffling, the train/validation split and the batch_size/drop_remainder settings
191/// are all inherited from the loader's configuration.
192/// \param outputFormat Either "ttree" or "rntuple".
193void RDataLoaderEngine::Save(std::string_view dataset_name, std::string_view filename, bool isTraining,
194 std::string_view outputFormat)
195{
196 // Cannot invoke mid-epoch
197 if (isTraining ? IsTrainingActive() : IsValidationActive())
198 throw std::runtime_error("RDataLoaderEngine::Save: this dataset is already being iterated elsewhere "
199 "(e.g. inside a training loop). Finish or stop that iteration before saving.");
200
201 REpochGuard epoch(*this, isTraining);
202 auto sink = CreateBatchSink(dataset_name, filename, fColumnLayout, outputFormat);
203
204 while (true) {
205 RFlat2DMatrix batch = isTraining ? GetTrainBatch() : GetValidationBatch();
206 if (batch.GetSize() == 0)
207 break;
208 sink->FillBatch(batch);
209 }
210
211 sink->Commit();
212}
213
214/// \brief Activate the training epoch by starting the batchloader.
216{
217 {
218 std::lock_guard<std::mutex> lock(fLoadingMutex);
221 if (!fLoadEager) {
222 // Shuffle the cluster indices at the beginning of each epoch
223 fClusterLoader->ShuffleTrainingClusters(fTrainingEpochCount++);
224 }
225 }
226
227 fTrainingBatchLoader->Activate();
228 fLoadingCondition.notify_all();
229}
230
232{
233 {
234 std::lock_guard<std::mutex> lock(fLoadingMutex);
235 fTrainingEpochActive = false;
236 }
237
238 fTrainingBatchLoader->Reset();
239 fTrainingBatchLoader->DeActivate();
240 fLoadingCondition.notify_all();
241}
242
244{
245 {
246 std::lock_guard<std::mutex> lock(fLoadingMutex);
249 if (!fLoadEager) {
250 fClusterLoader->ShuffleValidationClusters(fValidationEpochCount++);
251 }
252 }
253
254 fValidationBatchLoader->Activate();
255 fLoadingCondition.notify_all();
256}
257
259{
260 {
261 std::lock_guard<std::mutex> lock(fLoadingMutex);
263 }
264
265 fValidationBatchLoader->Reset();
266 fValidationBatchLoader->DeActivate();
267 fLoadingCondition.notify_all();
268}
269
270/// \brief Main loop for loading clusters and creating batches.
271/// The producer (loading thread) will keep loading clusters and creating batches until the end of the epoch is
272/// reached, or the generator is deactivated.
274{
275 std::unique_lock<std::mutex> lock(fLoadingMutex);
276
277 while (true) {
278 // Wait until we have work or shutdown
279 fLoadingCondition.wait(lock, [&] {
280 return !fIsActive ||
281 (fTrainingEpochActive && fTrainingClusterIdx < fClusterLoader->GetNumTrainingClusters()) ||
282 (fValidationEpochActive && fValidationClusterIdx < fClusterLoader->GetNumValidationClusters());
283 });
284
285 if (!fIsActive) {
286 break;
287 }
288
289 // Helper: check if validation queue below watermark and needs the producer
290 auto validationEmpty = [&] {
291 if (!fValidationEpochActive || fValidationClusterIdx >= fClusterLoader->GetNumValidationClusters())
292 return false;
293 if (fValidationBatchLoader->isProducerDone())
294 return false;
295 return fValidationBatchLoader->GetNumBatchQueue() < fLowWatermark / fBatchSize;
296 };
297
298 // -- TRAINING --
300 const std::size_t numTrainingClusters = fClusterLoader->GetNumTrainingClusters();
301
302 while (true) {
303 // Stop conditions (shutdown or epoch end)
305 break;
306
307 // No more chunks to load: signal consumers
308 if (fTrainingClusterIdx >= numTrainingClusters) {
309 fTrainingBatchLoader->MarkProducerDone();
310 break;
311 }
312
313 // In the case of training prefetching, we could start requesting data for the next training loop while
314 // validation is active and might need data. To avoid getting stuck in the training loop, we check if the
315 // validation queue is below watermark and if so, we break out of the training loop.
316 if (validationEmpty()) {
317 break;
318 }
319
320 // If queue is not empty, wait until it drains below watermark, or validation needs data, or we are
321 // deactivated.
322 if (fTrainingBatchLoader->GetNumBatchQueue() >= fLowWatermark / fBatchSize) {
323 fLoadingCondition.wait(lock, [&] {
324 return !fIsActive || !fTrainingEpochActive ||
325 fTrainingBatchLoader->GetNumBatchQueue() < (fLowWatermark / fBatchSize) || validationEmpty();
326 });
327 continue;
328 }
329
330 // Accumulate clusters to load, enough to fill the buffer, or until we run out of clusters
331 std::vector<RClusterRange> trainClustersToLoad;
332 std::size_t accumulatedEntries = 0;
333 const bool discovering = !fClusterLoader->IsSplitDiscovered();
334 while (fTrainingClusterIdx < numTrainingClusters && accumulatedEntries < fBufferCapacity &&
335 (!discovering || trainClustersToLoad.empty())) {
336 const auto &cluster = fClusterLoader->GetTrainingClusters()[fTrainingClusterIdx++];
337 trainClustersToLoad.push_back(cluster);
338 accumulatedEntries += cluster.GetNumEntries();
339 }
340
341 const bool isLastBuffer = (fTrainingClusterIdx >= numTrainingClusters);
342
343 // Release lock while reading and loading data to allow the consumer to access the queue freely in
344 // parallel. The loading thread re-acquires the lock in CreateBatches when it needs to push batches to
345 // the queue.
346 lock.unlock();
347 // reserve the buffer and let the clusters be appended to it, filters can only make them smaller
348 RFlat2DMatrix stagingBuffer;
349 stagingBuffer.Reserve(accumulatedEntries, fClusterLoader->GetNumChunkCols());
350 std::size_t rowOffset = 0;
351
352 // without shuffling, the clusters are contiguous, so we merge them to run in the same event loop;
353 // otherwise (shuffling is enabled), it is one event loop per cluster
354 if (!discovering) {
355 trainClustersToLoad = MergeConsecutiveClusters(trainClustersToLoad);
356 }
357
358 for (auto &cluster : trainClustersToLoad) {
359 auto loadedEntries =
360 fClusterLoader->LoadTrainingClusterInto(stagingBuffer, cluster.rdfIdx, cluster.start, cluster.end);
361 if (discovering) {
362 // For the first epoch, we might discover that the cluster has fewer entries than expected because
363 // of filters
364 cluster.SetNumEntries(loadedEntries);
365 }
366 rowOffset += cluster.GetNumEntries();
367 }
368
369 if (discovering && fNumTrainingEntries == 0 && fClusterLoader->GetNumTrainingEntries() > 0) {
370 fNumTrainingEntries = fClusterLoader->GetNumTrainingEntries();
371 fNumValidationEntries = fClusterLoader->GetNumValidationEntries();
372 fTrainingBatchLoader->RecalculateBatchCounts(fNumTrainingEntries);
373 fValidationBatchLoader->RecalculateBatchCounts(fNumValidationEntries);
374 }
375
376 stagingBuffer.Resize(rowOffset, fClusterLoader->GetNumChunkCols());
377
378 // the batches read the buffer in shuffled order, so it needs no shuffled copy
379 fTrainingBatchLoader->CreateBatches(stagingBuffer,
380 fTensorOperators->MakePermutation(stagingBuffer.GetRows()), 0,
381 stagingBuffer.GetRows(), isLastBuffer);
382
383 // Re-acquire the lock before the next iteration to check conditions and update indices
384 lock.lock();
385
386 if (isLastBuffer && discovering) {
387 fClusterLoader->FinaliseSplitDiscovery();
388 }
389 }
390 }
391
392 // -- VALIDATION --
394 const std::size_t numValidationClusters = fClusterLoader->GetNumValidationClusters();
395
396 while (true) {
397 // Stop conditions (shutdown or epoch end)
399 break;
400
401 // No more chunks to load: signal consumers
402 if (fValidationClusterIdx >= numValidationClusters) {
403 fValidationBatchLoader->MarkProducerDone();
404 break;
405 }
406
407 // If queue is not hungry, wait until it drains below watermark, or we are deactivated
408 if (fValidationBatchLoader->GetNumBatchQueue() >= (fLowWatermark / fBatchSize)) {
409 fLoadingCondition.wait(lock, [&] {
410 return !fIsActive || !fValidationEpochActive ||
411 fValidationBatchLoader->GetNumBatchQueue() < (fLowWatermark / fBatchSize);
412 });
413 continue;
414 }
415
416 // Accumulate clusters to load, enough to fill the buffer, or until we run out of clusters
417 std::vector<RClusterRange> valClustersToLoad;
418 std::size_t accumulatedEntries = 0;
419 while (fValidationClusterIdx < numValidationClusters && accumulatedEntries < fBufferCapacity) {
420 const auto &cluster = fClusterLoader->GetValidationClusters()[fValidationClusterIdx++];
421 valClustersToLoad.push_back(cluster);
422 accumulatedEntries += cluster.GetNumEntries();
423 }
424
425 const bool isLastBuffer = (fValidationClusterIdx >= numValidationClusters);
426
427 lock.unlock();
428
429 RFlat2DMatrix stagingBuffer;
430 stagingBuffer.Reserve(accumulatedEntries, fClusterLoader->GetNumChunkCols());
431 std::size_t rowOffset = 0;
432
433 valClustersToLoad = MergeConsecutiveClusters(valClustersToLoad);
434 for (const auto &cluster : valClustersToLoad) {
435 fClusterLoader->LoadValidationClusterInto(stagingBuffer, cluster.rdfIdx, cluster.start, cluster.end);
436 rowOffset += cluster.GetNumEntries();
437 }
438 stagingBuffer.Resize(rowOffset, fClusterLoader->GetNumChunkCols());
439
440 fValidationBatchLoader->CreateBatches(stagingBuffer,
441 fTensorOperators->MakePermutation(stagingBuffer.GetRows()), 0,
442 stagingBuffer.GetRows(), isLastBuffer);
443
444 lock.lock();
445 }
446 }
447 }
448}
449
450/// \brief Create training batches by first loading a chunk (see RClusterLoader) and split it into batches (see
451/// RBatchLoader)
453{
454 fTrainingBatchLoader->Activate();
455
456 if (fLoadEager) {
457 // the batches are created one at a time when they are requested, see GetTrainBatch()
458 if (fSampleType == "") {
459 // the batches read the training rows of the dataset in shuffled order, so they need no copy
461 }
462
463 else {
466 }
467 }
468}
469
470/// \brief Creates validation batches by first loading a chunk (see RClusterLoader), and then split it into batches
471/// (see RBatchLoader)
473{
474 fValidationBatchLoader->Activate();
475
476 if (fLoadEager) {
477 // the batches are created one at a time when they are requested, see GetValidationBatch()
478 if (fSampleType == "") {
480 }
481
482 else {
485 }
486 }
487}
488
489/// \brief The eager epoch reading the given rows of the dataset, in shuffled order when shuffling
491{
492 // contiguous rows read in order are a range of the dataset, they need no permutation
493 if (!fShuffle && (rows.empty() || rows.back() - rows.front() + 1 == rows.size())) {
494 const std::size_t beginRow = rows.empty() ? 0 : rows.front();
495 return {&fDataset, {}, beginRow, beginRow + rows.size()};
496 }
497
498 // shuffling a copy of the rows gives the same order as reading them through a shuffled permutation
499 std::vector<std::size_t> permutation = rows;
500 fTensorOperators->ShuffleIndices(permutation);
501 const std::size_t numRows = permutation.size();
502 return {&fDataset, std::move(permutation), 0, numRows};
503}
504
505/// \brief Create the next batch of an eager epoch, unless one is already queued or the epoch is done
507{
508 if (!epoch.fSource) {
509 return;
510 }
511
512 // a slice of one batch size gives a full batch, the last slice also gives the leftover batch
513 while (batchLoader.GetNumBatchQueue() == 0 && !batchLoader.isProducerDone()) {
514 const std::size_t endRow =
515 std::min(epoch.fNextRow + std::max<std::size_t>(batchLoader.GetBatchSize(), 1), epoch.fEndRow);
516 batchLoader.CreateBatches(*epoch.fSource, epoch.fPermutation, epoch.fNextRow, endRow, endRow == epoch.fEndRow);
517 epoch.fNextRow = endRow;
518 if (endRow == epoch.fEndRow) {
519 batchLoader.MarkProducerDone();
520 }
521 }
522}
523
524/// \brief Loads a training batch from the queue
526{
527 if (fLoadEager) {
529 }
530
531 // Get next batch if available
532 return fTrainingBatchLoader->GetBatch();
533}
534
535/// \brief Loads a validation batch from the queue
537{
538 if (fLoadEager) {
540 }
541
542 // Get next batch if available
543 return fValidationBatchLoader->GetBatch();
544}
545
547{
548 return fTrainingBatchLoader->GetNumBatches();
549}
550
552{
553 return fValidationBatchLoader->GetNumBatches();
554}
555
557{
558 return fTrainingBatchLoader->GetNumRemainderRows();
559}
560
562{
563 return fValidationBatchLoader->GetNumRemainderRows();
564}
565
567{
568 std::lock_guard<std::mutex> lock(fLoadingMutex);
569 return fIsActive;
570}
571
573{
574 std::lock_guard<std::mutex> lock(fLoadingMutex);
576}
577
579{
580 std::lock_guard<std::mutex> lock(fLoadingMutex);
582}
583
584} // namespace ROOT::Experimental::Internal::ML
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char filename
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h offset
Option_t Option_t width
Building and loading the batches from loaded chunks in RChunkLoader.
void CreateBatches(const RFlat2DMatrix &chunkTensor, const std::vector< std::size_t > &permutation, std::size_t beginRow, std::size_t endRow, bool isLastBatch)
Creating the batches from a chunk and add them to the queue.
void MarkProducerDone()
Signal that the producer has finished pushing all batches for this epoch.
In this class, the processes of loading clusters (see RClusterLoader) and creating batches from those...
std::unique_ptr< RFlat2DMatrixOperators > fTensorOperators
RFlat2DMatrix GetValidationBatch()
Loads a validation batch from the queue.
void Activate()
Activate the loading process by spawning the loading thread.
void Save(std::string_view dataset_name, std::string_view filename, bool isTraining, std::string_view outputFormat)
Materialize one train/test split to disk by draining a full epoch through the normal batch pipeline a...
std::vector< RColumnLayout > MakeColumnLayout()
Describe how the loader's columns map onto a batch-tensor row.
std::vector< std::vector< std::size_t > > fValidationRows
void LoadData()
Main loop for loading clusters and creating batches.
void ActivateTrainingEpoch()
Activate the training epoch by starting the batchloader.
void CreateValidationBatches()
Creates validation batches by first loading a chunk (see RClusterLoader), and then split it into batc...
RDataLoaderEngine(const std::vector< ROOT::RDF::RNode > &rdfs, const std::size_t batchSize, const std::size_t batchesInMemory, const std::vector< std::string > &cols, const std::vector< std::size_t > &vecSizes={}, const float vecPadding=0.0, const float testSize=0.0, bool shuffle=true, bool dropRemainder=true, const std::size_t setSeed=0, bool loadEager=false, std::string sampleType="", float sampleRatio=1.0, bool replacement=false)
RFlat2DMatrix GetTrainBatch()
Loads a training batch from the queue.
std::vector< std::vector< std::size_t > > fTrainingRows
REagerEpoch MakeEagerEpoch(const std::vector< std::size_t > &rows)
The eager epoch reading the given rows of the dataset, in shuffled order when shuffling.
void CreateNextEagerBatch(RBatchLoader &batchLoader, REagerEpoch &epoch)
Create the next batch of an eager epoch, unless one is already queued or the epoch is done.
void CreateTrainBatches()
Create training batches by first loading a chunk (see RClusterLoader) and split it into batches (see ...
std::unique_ptr< RBatchSink > CreateBatchSink(std::string_view dataset_name, std::string_view filename, std::vector< RColumnLayout > layout, std::string_view format)
Create the sink matching format.
Rows of an eager epoch, turned into batches as they are requested.
Opens a training or validation epoch and closes it again when done.
Wrapper around ROOT::RVec<float> representing a 2D matrix.
void Resize(std::size_t rows, std::size_t cols)
void Reserve(std::size_t rows, std::size_t cols)