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
14/// \brief Describe how the loader's columns map onto a batch-tensor row.
15std::vector<RColumnLayout> RDataLoaderEngine::MakeColumnLayout()
16{
17 std::vector<RColumnLayout> layout;
18 layout.reserve(fCols.size());
19
20 std::size_t vecIdx = 0;
21 std::size_t offset = 0;
22 for (const auto &col : fCols) {
23 const bool isVector = fRdfs[0].GetColumnType(col).find("RVec") != std::string::npos;
24 const std::size_t width = isVector ? fVecSizes[vecIdx++] : 1;
25 layout.push_back({col, offset, width, isVector});
26 offset += width;
27 }
28
29 return layout;
30}
31
33 : fEngine(engine), fIsTraining(isTraining)
34{
35 // Same order as the pythonization's epoch context managers
37 if (fIsTraining) {
40 } else {
43 }
44}
45
47{
48 if (fIsTraining)
49 fEngine.DeActivateTrainingEpoch();
50 else
51 fEngine.DeActivateValidationEpoch();
52}
53
54RDataLoaderEngine::RDataLoaderEngine(const std::vector<ROOT::RDF::RNode> &rdfs, const std::size_t batchSize,
55 const std::size_t batchesInMemory, const std::vector<std::string> &cols,
56 const std::vector<std::size_t> &vecSizes, const float vecPadding,
57 const float testSize, bool shuffle, bool dropRemainder, const std::size_t setSeed,
58 bool loadEager, std::string sampleType, float sampleRatio, bool replacement)
59 : fCols(cols),
60 fVecSizes(vecSizes),
61 fBatchSize(batchSize),
62 fSetSeed(setSeed),
63 fTestSize(testSize),
64 fRdfs(rdfs),
65 fDropRemainder(dropRemainder),
66 fShuffle(shuffle),
67 fLoadEager(loadEager),
68 fSampleType(sampleType),
69 fSampleRatio(sampleRatio),
70 fReplacement(replacement)
71{
72 fTensorOperators = std::make_unique<RFlat2DMatrixOperators>(fShuffle, fSetSeed);
73
74 // describe the columns before the eager path releases the dataframes
76
77 if (fLoadEager) {
79 std::make_unique<RDatasetLoader>(fRdfs, fTestSize, fCols, fVecSizes, vecPadding, fShuffle, fSetSeed);
80 fDatasetLoader->SplitDatasets();
81
82 if (fSampleType == "") {
83 fDatasetLoader->ConcatenateDatasets();
84
85 fTrainingDataset = fDatasetLoader->ReleaseTrainingDataset();
86 fValidationDataset = fDatasetLoader->ReleaseValidationDataset();
87
88 fNumTrainingEntries = fDatasetLoader->GetNumTrainingEntries();
89 fNumValidationEntries = fDatasetLoader->GetNumValidationEntries();
90 }
91
92 else {
93 fTrainingDatasets = fDatasetLoader->ReleaseTrainingDatasets();
94 fValidationDatasets = fDatasetLoader->ReleaseValidationDatasets();
95
100
101 fNumTrainingEntries = fTrainingSampler->GetNumEntries();
102 fNumValidationEntries = fValidationSampler->GetNumEntries();
103 }
104
105 // the dataset is in memory now, release the objects used to create it
106 fDatasetLoader.reset();
107 fRdfs.clear();
108 }
109
110 else {
111 // scan cluster boundaries
113 std::make_unique<RClusterLoader>(fRdfs, fCols, fVecSizes, vecPadding, fTestSize, fShuffle, fSetSeed);
114
115 // derive buffer quantities
116 fBufferCapacity = fBatchSize * batchesInMemory;
117 // at least one batch, otherwise the refill threshold rounds down to 0 and nothing is ever loaded
119
120 // split cluster list into training and validation
121 fClusterLoader->SplitDataset();
122 fNumTrainingEntries = fClusterLoader->GetNumTrainingEntries();
123 fNumValidationEntries = fClusterLoader->GetNumValidationEntries();
124 }
125
128 fValidationBatchLoader = std::make_unique<RBatchLoader>(fBatchSize, fCols, fLoadingMutex, fLoadingCondition,
130}
131
136
138{
139 {
140 std::lock_guard<std::mutex> lock(fLoadingMutex);
141 if (!fIsActive)
142 return;
143 fIsActive = false;
144 }
145
146 fLoadingCondition.notify_all();
147
148 if (fLoadingThread) {
149 if (fLoadingThread->joinable()) {
150 fLoadingThread->join();
151 }
152 }
153
154 fLoadingThread.reset();
155}
156
157/// \brief Activate the loading process by spawning the loading thread.
159{
160 {
161 std::lock_guard<std::mutex> lock(fLoadingMutex);
162 if (fIsActive)
163 return;
164
165 fIsActive = true;
166 }
167
168 if (fLoadEager) {
169 return;
170 }
171
172 fLoadingThread = std::make_unique<std::thread>(&RDataLoaderEngine::LoadData, this);
173}
174
175/// \brief Materialize one train/test split to disk by draining a full epoch through the normal batch
176/// pipeline and Fill() each batch into \p filename instead of yielding it.
177///
178/// Filters, shuffling, the train/validation split and the batch_size/drop_remainder settings
179/// are all inherited from the loader's configuration.
180/// \param outputFormat Either "ttree" or "rntuple".
181void RDataLoaderEngine::Save(std::string_view dataset_name, std::string_view filename, bool isTraining,
182 std::string_view outputFormat)
183{
184 // Cannot invoke mid-epoch
185 if (isTraining ? IsTrainingActive() : IsValidationActive())
186 throw std::runtime_error("RDataLoaderEngine::Save: this dataset is already being iterated elsewhere "
187 "(e.g. inside a training loop). Finish or stop that iteration before saving.");
188
189 REpochGuard epoch(*this, isTraining);
190 auto sink = CreateBatchSink(dataset_name, filename, fColumnLayout, outputFormat);
191
192 while (true) {
193 RFlat2DMatrix batch = isTraining ? GetTrainBatch() : GetValidationBatch();
194 if (batch.GetSize() == 0)
195 break;
196 sink->FillBatch(batch);
197 }
198
199 sink->Commit();
200}
201
202/// \brief Activate the training epoch by starting the batchloader.
204{
205 {
206 std::lock_guard<std::mutex> lock(fLoadingMutex);
209 if (!fLoadEager) {
210 // Shuffle the cluster indices at the beginning of each epoch
211 fClusterLoader->ShuffleTrainingClusters(fTrainingEpochCount++);
212 }
213 }
214
215 fTrainingBatchLoader->Activate();
216 fLoadingCondition.notify_all();
217}
218
220{
221 {
222 std::lock_guard<std::mutex> lock(fLoadingMutex);
223 fTrainingEpochActive = false;
224 }
225
226 fTrainingBatchLoader->Reset();
227 fTrainingBatchLoader->DeActivate();
228 fLoadingCondition.notify_all();
229}
230
232{
233 {
234 std::lock_guard<std::mutex> lock(fLoadingMutex);
237 if (!fLoadEager) {
238 fClusterLoader->ShuffleValidationClusters(fValidationEpochCount++);
239 }
240 }
241
242 fValidationBatchLoader->Activate();
243 fLoadingCondition.notify_all();
244}
245
247{
248 {
249 std::lock_guard<std::mutex> lock(fLoadingMutex);
251 }
252
253 fValidationBatchLoader->Reset();
254 fValidationBatchLoader->DeActivate();
255 fLoadingCondition.notify_all();
256}
257
258/// \brief Main loop for loading clusters and creating batches.
259/// The producer (loading thread) will keep loading clusters and creating batches until the end of the epoch is
260/// reached, or the generator is deactivated.
262{
263 std::unique_lock<std::mutex> lock(fLoadingMutex);
264
265 while (true) {
266 // Wait until we have work or shutdown
267 fLoadingCondition.wait(lock, [&] {
268 return !fIsActive ||
269 (fTrainingEpochActive && fTrainingClusterIdx < fClusterLoader->GetNumTrainingClusters()) ||
270 (fValidationEpochActive && fValidationClusterIdx < fClusterLoader->GetNumValidationClusters());
271 });
272
273 if (!fIsActive) {
274 break;
275 }
276
277 // Helper: check if validation queue below watermark and needs the producer
278 auto validationEmpty = [&] {
279 if (!fValidationEpochActive || fValidationClusterIdx >= fClusterLoader->GetNumValidationClusters())
280 return false;
281 if (fValidationBatchLoader->isProducerDone())
282 return false;
283 return fValidationBatchLoader->GetNumBatchQueue() < fLowWatermark / fBatchSize;
284 };
285
286 // -- TRAINING --
288 const std::size_t numTrainingClusters = fClusterLoader->GetNumTrainingClusters();
289
290 while (true) {
291 // Stop conditions (shutdown or epoch end)
293 break;
294
295 // No more chunks to load: signal consumers
296 if (fTrainingClusterIdx >= numTrainingClusters) {
297 fTrainingBatchLoader->MarkProducerDone();
298 break;
299 }
300
301 // In the case of training prefetching, we could start requesting data for the next training loop while
302 // validation is active and might need data. To avoid getting stuck in the training loop, we check if the
303 // validation queue is below watermark and if so, we break out of the training loop.
304 if (validationEmpty()) {
305 break;
306 }
307
308 // If queue is not empty, wait until it drains below watermark, or validation needs data, or we are
309 // deactivated.
310 if (fTrainingBatchLoader->GetNumBatchQueue() >= fLowWatermark / fBatchSize) {
311 fLoadingCondition.wait(lock, [&] {
312 return !fIsActive || !fTrainingEpochActive ||
313 fTrainingBatchLoader->GetNumBatchQueue() < (fLowWatermark / fBatchSize) || validationEmpty();
314 });
315 continue;
316 }
317
318 // Accumulate clusters to load, enough to fill the buffer, or until we run out of clusters
319 std::vector<RClusterRange> trainClustersToLoad;
320 std::size_t accumulatedEntries = 0;
321 const bool discovering = !fClusterLoader->IsSplitDiscovered();
322 while (fTrainingClusterIdx < numTrainingClusters && accumulatedEntries < fBufferCapacity &&
323 (!discovering || trainClustersToLoad.empty())) {
324 const auto &cluster = fClusterLoader->GetTrainingClusters()[fTrainingClusterIdx++];
325 trainClustersToLoad.push_back(cluster);
326 accumulatedEntries += cluster.GetNumEntries();
327 }
328
329 const bool isLastBuffer = (fTrainingClusterIdx >= numTrainingClusters);
330
331 // Release lock while reading and loading data to allow the consumer to access the queue freely in
332 // parallel. The loading thread re-acquires the lock in CreateBatches when it needs to push batches to
333 // the queue.
334 lock.unlock();
335 // reserve the buffer and let the clusters be appended to it, filters can only make them smaller
336 RFlat2DMatrix stagingBuffer;
337 stagingBuffer.fRVec.reserve(accumulatedEntries * fClusterLoader->GetNumChunkCols());
338 std::size_t rowOffset = 0;
339
340 for (auto &cluster : trainClustersToLoad) {
341 auto loadedEntries =
342 fClusterLoader->LoadTrainingClusterInto(stagingBuffer, cluster.rdfIdx, cluster.start, cluster.end);
343 if (discovering) {
344 // For the first epoch, we might discover that the cluster has fewer entries than expected because
345 // of filters
346 cluster.SetNumEntries(loadedEntries);
347 }
348 rowOffset += cluster.GetNumEntries();
349 }
350
351 if (discovering && fNumTrainingEntries == 0 && fClusterLoader->GetNumTrainingEntries() > 0) {
352 fNumTrainingEntries = fClusterLoader->GetNumTrainingEntries();
353 fNumValidationEntries = fClusterLoader->GetNumValidationEntries();
354 fTrainingBatchLoader->RecalculateBatchCounts(fNumTrainingEntries);
355 fValidationBatchLoader->RecalculateBatchCounts(fNumValidationEntries);
356 }
357
358 stagingBuffer.Resize(rowOffset, fClusterLoader->GetNumChunkCols());
359
360 RFlat2DMatrix shuffledStagingBuffer;
361 fTrainingBatchLoader->CreateBatches(fTensorOperators->ShuffleTensor(shuffledStagingBuffer, stagingBuffer),
362 isLastBuffer);
363
364 // Re-acquire the lock before the next iteration to check conditions and update indices
365 lock.lock();
366
367 if (isLastBuffer && discovering) {
368 fClusterLoader->FinaliseSplitDiscovery();
369 }
370 }
371 }
372
373 // -- VALIDATION --
375 const std::size_t numValidationClusters = fClusterLoader->GetNumValidationClusters();
376
377 while (true) {
378 // Stop conditions (shutdown or epoch end)
380 break;
381
382 // No more chunks to load: signal consumers
383 if (fValidationClusterIdx >= numValidationClusters) {
384 fValidationBatchLoader->MarkProducerDone();
385 break;
386 }
387
388 // If queue is not hungry, wait until it drains below watermark, or we are deactivated
389 if (fValidationBatchLoader->GetNumBatchQueue() >= (fLowWatermark / fBatchSize)) {
390 fLoadingCondition.wait(lock, [&] {
391 return !fIsActive || !fValidationEpochActive ||
392 fValidationBatchLoader->GetNumBatchQueue() < (fLowWatermark / fBatchSize);
393 });
394 continue;
395 }
396
397 // Accumulate clusters to load, enough to fill the buffer, or until we run out of clusters
398 std::vector<RClusterRange> valClustersToLoad;
399 std::size_t accumulatedEntries = 0;
400 while (fValidationClusterIdx < numValidationClusters && accumulatedEntries < fBufferCapacity) {
401 const auto &cluster = fClusterLoader->GetValidationClusters()[fValidationClusterIdx++];
402 valClustersToLoad.push_back(cluster);
403 accumulatedEntries += cluster.GetNumEntries();
404 }
405
406 const bool isLastBuffer = (fValidationClusterIdx >= numValidationClusters);
407
408 lock.unlock();
409
410 RFlat2DMatrix stagingBuffer;
411 stagingBuffer.fRVec.reserve(accumulatedEntries * fClusterLoader->GetNumChunkCols());
412 std::size_t rowOffset = 0;
413
414 for (const auto &cluster : valClustersToLoad) {
415 fClusterLoader->LoadValidationClusterInto(stagingBuffer, cluster.rdfIdx, cluster.start, cluster.end);
416 rowOffset += cluster.GetNumEntries();
417 }
418 stagingBuffer.Resize(rowOffset, fClusterLoader->GetNumChunkCols());
419
420 RFlat2DMatrix shuffledStagingBuffer;
421 fValidationBatchLoader->CreateBatches(fTensorOperators->ShuffleTensor(shuffledStagingBuffer, stagingBuffer),
422 isLastBuffer);
423
424 lock.lock();
425 }
426 }
427 }
428}
429
430/// \brief Create training batches by first loading a chunk (see RClusterLoader) and split it into batches (see
431/// RBatchLoader)
433{
434 fTrainingBatchLoader->Activate();
435
436 if (fLoadEager) {
438 if (fSampleType == "") {
440 }
441
442 else {
444 }
445
446 fTrainingBatchLoader->CreateBatches(*source, true);
447 fTrainingBatchLoader->MarkProducerDone();
448 }
449}
450
451/// \brief Creates validation batches by first loading a chunk (see RClusterLoader), and then split it into batches
452/// (see RBatchLoader)
454{
455 fValidationBatchLoader->Activate();
456
457 if (fLoadEager) {
459 if (fSampleType == "") {
461 }
462
463 else {
465 }
466
467 fValidationBatchLoader->CreateBatches(*source, true);
468 fValidationBatchLoader->MarkProducerDone();
469 }
470}
471
472/// \brief Loads a training batch from the queue
474{
475 // Get next batch if available
476 return fTrainingBatchLoader->GetBatch();
477}
478
479/// \brief Loads a validation batch from the queue
481{
482 // Get next batch if available
483 return fValidationBatchLoader->GetBatch();
484}
485
487{
488 return fTrainingBatchLoader->GetNumBatches();
489}
490
492{
493 return fValidationBatchLoader->GetNumBatches();
494}
495
497{
498 return fTrainingBatchLoader->GetNumRemainderRows();
499}
500
502{
503 return fValidationBatchLoader->GetNumRemainderRows();
504}
505
507{
508 std::lock_guard<std::mutex> lock(fLoadingMutex);
509 return fIsActive;
510}
511
513{
514 std::lock_guard<std::mutex> lock(fLoadingMutex);
516}
517
519{
520 std::lock_guard<std::mutex> lock(fLoadingMutex);
522}
523
524} // 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
void reserve(size_type N)
Definition RVec.hxx:606
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.
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.
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.
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)