Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RooBatchCompute.cxx
Go to the documentation of this file.
1/*
2 * Project: RooFit
3 * Authors:
4 * Emmanouil Michalainas, CERN, September 2020
5 *
6 * Copyright (c) 2021, CERN
7 *
8 * Redistribution and use in source and binary forms,
9 * with or without modification, are permitted according to the terms
10 * listed in LICENSE (http://roofit.sourceforge.net/license.txt)
11 */
12
13/**
14\file RooBatchCompute.cxx
15\class RbcClass
16\ingroup roofit_dev_docs_batchcompute
17
18This file contains the code for cpu computations using the RooBatchCompute library.
19**/
20
21#include "RooBatchCompute.h"
22#include "RooNaNPacker.h"
23#include "Batches.h"
24
25#include <ROOT/RConfig.hxx>
26#include <RConfigure.h>
27
28#ifdef R__USE_IMT
29#include <ROOT/TSeq.hxx>
31#endif
32
33#include <Math/Util.h>
34
35#include <algorithm>
36#include <functional>
37#include <map>
38#include <memory>
39#include <mutex>
40#include <queue>
41#include <sstream>
42#include <stdexcept>
43
44#include <vector>
45
46#ifndef RF_ARCH
47#error "RF_ARCH should always be defined"
48#endif
49
50namespace RooBatchCompute {
51namespace RF_ARCH {
52
53namespace {
54
55void fillBatches(Batches &batches, double *output, size_t nEvents, std::size_t nBatches, ArgSpan extraArgs)
56{
57 batches.extra = extraArgs.data();
58 batches.nEvents = nEvents;
59 batches.nBatches = nBatches;
60 batches.nExtra = extraArgs.size();
61 batches.output = output;
62}
63
64void fillArrays(std::span<Batch> arrays, VarSpan vars, std::size_t nEvents)
65{
66 for (std::size_t i = 0; i < vars.size(); i++) {
67 arrays[i]._array = vars[i].data();
68 arrays[i]._isVector = vars[i].empty() || vars[i].size() >= nEvents;
69 }
70}
71
72inline void advance(Batches &batches, std::size_t nEvents)
73{
74 for (std::size_t i = 0; i < batches.nBatches; i++) {
75 Batch &arg = batches.args[i];
76 arg._array += arg._isVector * nEvents;
77 }
78 batches.output += nEvents;
79}
80
81/// Run one compute function over the event range [begin, begin + count).
82/// The `_isVector` flags of the inputs are determined by the total number of
83/// events, such that the same inputs are considered per-event arrays no matter
84/// how the full range is split into sub-ranges.
85void computeRange(void (*computeFn)(Batches &), double *output, std::size_t totalNEvents, VarSpan vars,
86 ArgSpan extraArgs, std::size_t begin, std::size_t count)
87{
88 std::vector<Batch> arrays(vars.size());
90 fillBatches(batches, output, count, vars.size(), extraArgs);
92 batches.args = arrays.data();
93 advance(batches, begin);
94
95 std::size_t events = count;
96 batches.nEvents = bufferSize;
97 while (events > bufferSize) {
100 events -= bufferSize;
101 }
102 batches.nEvents = events;
104}
105
106#ifdef R__USE_IMT
107
108// The number of events per task when computing multi-threaded. The chunk
109// boundaries must not depend on the number of threads, such that also the
110// results of the multi-threaded reductions are bitwise independent of the
111// requested number of threads.
112constexpr std::size_t parallelChunkSize = 16384;
113
114// Batches with fewer events than this are always evaluated single-threaded,
115// because the scheduling overhead would exceed the gain from parallelization.
116constexpr std::size_t minParallelSize = 2 * parallelChunkSize;
117
118std::size_t numChunks(std::size_t nEvents)
119{
120 return (nEvents + parallelChunkSize - 1) / parallelChunkSize;
121}
122
123/// Get a cached executor for the given number of threads. All executors share
124/// ROOT's global thread pool: the first executor created in the process
125/// determines its size, and requests for a different number of threads later
126/// on print a warning. Sharing one pool makes sure that parallel evaluations
127/// in several fits (or on top of user-level parallelism) running at the same
128/// time don't oversubscribe the machine.
130{
131 static std::mutex mutex;
132 // The map is deliberately leaked: this library is loaded with dlopen(), so
133 // there is no guaranteed destruction order between its statics and the TBB
134 // runtime underlying the executor, and destroying the thread pool after
135 // TBB tore down its scheduler can crash or hang at process exit.
136 static auto &executors = *new std::map<int, std::unique_ptr<ROOT::TThreadExecutor>>;
137 std::lock_guard<std::mutex> lock{mutex};
138 auto &executor = executors[nThreads];
139 if (!executor) {
140 executor = std::make_unique<ROOT::TThreadExecutor>(nThreads);
141 }
142 return *executor;
143}
144
145/// Call func(iChunk) in parallel, using up to nThreads threads, for the
146/// fixed-size event chunks covering [0, nEvents).
147template <class Func>
148void parallelForChunks(int nThreads, std::size_t nEvents, Func &&func)
149{
150 ROOT::TThreadExecutor &executor = executorFor(nThreads);
151 // Usually, the chunks are handed out to the threads dynamically one by one
152 // for optimal load balancing. But if the shared thread pool has more
153 // threads than requested (because something else initialized it with a
154 // higher concurrency before), the loop is instead split statically into at
155 // most nThreads tasks to still honor the requested number of threads.
156 const unsigned int nTasks = executor.GetPoolSize() > static_cast<unsigned int>(nThreads) ? nThreads : 0;
157 const ROOT::TSeq<unsigned int> chunkIndices{0u, static_cast<unsigned int>(numChunks(nEvents))};
158 executor.Foreach([&](unsigned int iChunk) { func(iChunk); }, chunkIndices, nTasks);
159}
160
161/// First event of a given fixed-size chunk.
162std::size_t chunkBegin(std::size_t iChunk)
163{
164 return iChunk * parallelChunkSize;
165}
166
167/// Number of events in a given fixed-size chunk (the last one can be shorter).
168std::size_t chunkSize(std::size_t iChunk, std::size_t nEvents)
169{
170 return std::min(parallelChunkSize, nEvents - chunkBegin(iChunk));
171}
172
173#endif // R__USE_IMT
174
175bool useParallelEvaluation(RooBatchCompute::Config const &cfg, std::size_t nEvents)
176{
177#ifdef R__USE_IMT
178 return cfg.nThreads() > 1 && nEvents >= minParallelSize;
179#else
180 (void)cfg;
181 (void)nEvents;
182 return false;
183#endif
184}
185
186} // namespace
187
188std::vector<void (*)(Batches &)> getFunctions();
189
190/// This class overrides some RooBatchComputeInterface functions, for the
191/// purpose of providing a CPU specific implementation of the library.
193public:
195 {
196 // Set the dispatch pointer to this instance of the library upon loading
197 dispatchCPU = this;
198 }
199
200 Architecture architecture() const override { return Architecture::RF_ARCH; };
201 std::string architectureName() const override
202 {
203 // transform to lower case to match the original architecture name passed to the compiler
204 std::string out = _R_QUOTEVAL_(RF_ARCH);
205 std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) { return std::tolower(c); });
206 return out;
207 };
208
209 void compute(Config const &, Computer computer, std::span<double> output, VarSpan vars, ArgSpan extraArgs) override;
210 double reduceSum(Config const &, InputArr input, size_t n) override;
211 ReduceNLLOutput reduceNLL(Config const &, std::span<const double> probas, std::span<const double> weights,
212 std::span<const double> offsetProbas) override;
213
214 std::unique_ptr<AbsBufferManager> createBufferManager() const override;
215
216 CudaInterface::CudaStream *newCudaStream() const override { throw std::bad_function_call(); }
217 void deleteCudaStream(CudaInterface::CudaStream *) const override { throw std::bad_function_call(); }
218 void synchronizeCudaStream(CudaInterface::CudaStream *) const override { throw std::bad_function_call(); }
219
220private:
221 const std::vector<void (*)(Batches &)> _computeFunctions;
222};
223
224/** Compute multiple values using optimized functions.
225This method creates a Batches object and passes it to the correct compute function.
226If the configuration requests more than one thread and the batch is large
227enough, the events are processed in fixed-size chunks by parallel tasks.
228\param cfg Configuration, steering among other things the number of threads.
229\param computer An enum specifying the compute function to be used.
230\param output The array where the computation results are stored.
231\param vars A std::span containing pointers to the variables involved in the computation.
232\param extraArgs An optional std::span containing extra double values that may participate in the computation. **/
233void RooBatchComputeClass::compute(Config const &cfg, Computer computer, std::span<double> output, VarSpan vars,
235{
236 const std::size_t nEvents = output.size();
238
239#ifdef R__USE_IMT
240 if (useParallelEvaluation(cfg, nEvents)) {
241 const std::size_t nChunks = numChunks(nEvents);
242
243 // Some compute functions use the extra arguments also as scratch space
244 // or as output parameters (e.g. for evaluation error counts), so every
245 // task works on its own copy and the differences to the original values
246 // are merged back afterwards.
247 const std::size_t nExtra = extraArgs.size();
248 std::vector<double> extraCopies(nChunks * nExtra);
249 for (std::size_t iChunk = 0; iChunk < nChunks; ++iChunk) {
250 std::copy(extraArgs.begin(), extraArgs.end(), extraCopies.begin() + iChunk * nExtra);
251 }
252
253 parallelForChunks(cfg.nThreads(), nEvents, [&](std::size_t iChunk) {
254 computeRange(computeFn, output.data(), nEvents, vars, {extraCopies.data() + iChunk * nExtra, nExtra},
255 chunkBegin(iChunk), chunkSize(iChunk, nEvents));
256 });
257
258 for (std::size_t k = 0; k < nExtra; ++k) {
259 double delta = 0.0;
260 for (std::size_t iChunk = 0; iChunk < nChunks; ++iChunk) {
261 delta += extraCopies[iChunk * nExtra + k] - extraArgs[k];
262 }
263 extraArgs[k] += delta;
264 }
265 return;
266 }
267#else
268 (void)cfg;
269#endif
270
271 computeRange(computeFn, output.data(), nEvents, vars, extraArgs, 0, nEvents);
272}
273
274namespace {
275
276inline std::pair<double, double> getLog(double prob, ReduceNLLOutput &out)
277{
278 if (prob <= 0.0) {
279 out.nNonPositiveValues++;
280 return {std::log(prob), -prob};
281 }
282
283 if (std::isinf(prob)) {
284 out.nInfiniteValues++;
285 }
286
287 if (std::isnan(prob)) {
288 out.nNaNValues++;
290 }
291
292 return {std::log(prob), 0.0};
293}
294
295} // namespace
296
297double RooBatchComputeClass::reduceSum(Config const &cfg, InputArr input, size_t n)
298{
299#ifdef R__USE_IMT
300 if (useParallelEvaluation(cfg, n)) {
301 const std::size_t nChunks = numChunks(n);
302 std::vector<ROOT::Math::KahanSum<double, 4u>> partials(nChunks);
303 parallelForChunks(cfg.nThreads(), n, [&](std::size_t iChunk) {
304 const std::size_t begin = chunkBegin(iChunk);
305 partials[iChunk] =
306 ROOT::Math::KahanSum<double, 4u>::Accumulate(input + begin, input + begin + chunkSize(iChunk, n));
307 });
308 // Combine the partial sums in fixed chunk order, so that the result
309 // doesn't depend on the number of threads.
311 for (auto const &partial : partials) {
312 total += partial;
313 }
314 return total.Sum();
315 }
316#else
317 (void)cfg;
318#endif
320}
321
322namespace {
323
324/// Accumulator for the negative log-likelihood reduction over one event range.
325struct NLLPartialResult {
327 double badness = 0.0;
328 ReduceNLLOutput counters;
329};
330
331void reduceNLLRange(std::span<const double> probas, std::span<const double> weights,
332 std::span<const double> offsetProbas, std::size_t begin, std::size_t end, NLLPartialResult &result)
333{
334 for (std::size_t i = begin; i < end; ++i) {
335
336 if (0. == weights[i])
337 continue;
338
339 std::pair<double, double> logOut = getLog(probas.size() == 1 ? probas[0] : probas[i], result.counters);
340 double term = logOut.first;
341 result.badness += logOut.second;
342
343 if (!offsetProbas.empty()) {
344 term -= std::log(offsetProbas[i]);
345 }
346
347 term *= -weights[i];
348
349 result.nllSum.Add(term);
350 }
351}
352
353} // namespace
354
355ReduceNLLOutput RooBatchComputeClass::reduceNLL(Config const &cfg, std::span<const double> probas,
356 std::span<const double> weights, std::span<const double> offsetProbas)
357{
358 const std::size_t n = weights.size();
359 NLLPartialResult result;
360
361#ifdef R__USE_IMT
362 if (useParallelEvaluation(cfg, n)) {
363 const std::size_t nChunks = numChunks(n);
364 std::vector<NLLPartialResult> partials(nChunks);
365 parallelForChunks(cfg.nThreads(), n, [&](std::size_t iChunk) {
366 const std::size_t begin = chunkBegin(iChunk);
367 reduceNLLRange(probas, weights, offsetProbas, begin, begin + chunkSize(iChunk, n), partials[iChunk]);
368 });
369 // Combine the partial results in fixed chunk order, so that the result
370 // doesn't depend on the number of threads.
371 for (auto const &partial : partials) {
372 result.nllSum += partial.nllSum;
373 result.badness += partial.badness;
374 result.counters.nInfiniteValues += partial.counters.nInfiniteValues;
375 result.counters.nNonPositiveValues += partial.counters.nNonPositiveValues;
376 result.counters.nNaNValues += partial.counters.nNaNValues;
377 }
378 } else {
379 reduceNLLRange(probas, weights, offsetProbas, 0, n, result);
380 }
381#else
382 (void)cfg;
383 reduceNLLRange(probas, weights, offsetProbas, 0, n, result);
384#endif
385
386 ReduceNLLOutput out = result.counters;
387 out.nllSum = result.nllSum.Sum();
388 out.nllSumCarry = result.nllSum.Carry();
389
390 if (result.badness != 0.) {
391 // Some events with evaluation errors. Return "badness" of errors.
393 out.nllSumCarry = 0.0;
394 }
395
396 return out;
397}
398
399namespace {
400
401class ScalarBufferContainer {
402public:
403 ScalarBufferContainer() {}
404 ScalarBufferContainer(std::size_t size)
405 {
406 if (size != 1)
407 throw std::runtime_error("ScalarBufferContainer can only be of size 1");
408 }
409
410 double const *hostReadPtr() const { return &_val; }
411 double const *deviceReadPtr() const { return &_val; }
412
413 double *hostWritePtr() { return &_val; }
414 double *deviceWritePtr() { return &_val; }
415
416 void assignFromHost(std::span<const double> input) { _val = input[0]; }
417 void assignFromDevice(std::span<const double>) { throw std::bad_function_call(); }
418
419private:
420 double _val;
421};
422
423class CPUBufferContainer {
424public:
425 CPUBufferContainer(std::size_t size) : _vec(size) {}
426
427 double const *hostReadPtr() const { return _vec.data(); }
428 double const *deviceReadPtr() const
429 {
430 throw std::bad_function_call();
431 return nullptr;
432 }
433
434 double *hostWritePtr() { return _vec.data(); }
435 double *deviceWritePtr()
436 {
437 throw std::bad_function_call();
438 return nullptr;
439 }
440
441 void assignFromHost(std::span<const double> input) { _vec.assign(input.begin(), input.end()); }
442 void assignFromDevice(std::span<const double>) { throw std::bad_function_call(); }
443
444private:
445 std::vector<double> _vec;
446};
447
448template <class Container>
449class BufferImpl : public AbsBuffer {
450public:
451 using Queue = std::queue<std::unique_ptr<Container>>;
452
453 BufferImpl(std::size_t size, Queue &queue) : _queue{queue}
454 {
455 if (_queue.empty()) {
456 _vec = std::make_unique<Container>(size);
457 } else {
458 _vec = std::move(_queue.front());
459 _queue.pop();
460 }
461 }
462
463 ~BufferImpl() override { _queue.emplace(std::move(_vec)); }
464
465 double const *hostReadPtr() const override { return _vec->hostReadPtr(); }
466 double const *deviceReadPtr() const override { return _vec->deviceReadPtr(); }
467
468 double *hostWritePtr() override { return _vec->hostWritePtr(); }
469 double *deviceWritePtr() override { return _vec->deviceWritePtr(); }
470
471 void assignFromHost(std::span<const double> input) override { _vec->assignFromHost(input); }
472 void assignFromDevice(std::span<const double> input) override { _vec->assignFromDevice(input); }
473
474 Container &vec() { return *_vec; }
475
476private:
477 std::unique_ptr<Container> _vec;
478 Queue &_queue;
479};
480
483
484struct BufferQueuesMaps {
485 std::map<std::size_t, ScalarBuffer::Queue> scalarBufferQueuesMap;
486 std::map<std::size_t, CPUBuffer::Queue> cpuBufferQueuesMap;
487};
488
489class BufferManager : public AbsBufferManager {
490
491public:
492 BufferManager() : _queuesMaps{std::make_unique<BufferQueuesMaps>()} {}
493
494 std::unique_ptr<AbsBuffer> makeScalarBuffer() override
495 {
496 return std::make_unique<ScalarBuffer>(1, _queuesMaps->scalarBufferQueuesMap[1]);
497 }
498 std::unique_ptr<AbsBuffer> makeCpuBuffer(std::size_t size) override
499 {
500 return std::make_unique<CPUBuffer>(size, _queuesMaps->cpuBufferQueuesMap[size]);
501 }
502 std::unique_ptr<AbsBuffer> makeGpuBuffer(std::size_t) override { throw std::bad_function_call(); }
503 std::unique_ptr<AbsBuffer> makePinnedBuffer(std::size_t, CudaInterface::CudaStream * = nullptr) override
504 {
505 throw std::bad_function_call();
506 }
507
508private:
509 std::unique_ptr<BufferQueuesMaps> _queuesMaps;
510};
511
512} // namespace
513
514std::unique_ptr<AbsBufferManager> RooBatchComputeClass::createBufferManager() const
515{
516 return std::make_unique<BufferManager>();
517}
518
519/// Static object to trigger the constructor which overwrites the dispatch pointer.
521
522} // End namespace RF_ARCH
523} // End namespace RooBatchCompute
#define RF_ARCH
#define _R_QUOTEVAL_(string)
Definition RConfig.hxx:426
#define c(i)
Definition RSha256.hxx:101
std::vector< double > _vec
double _val
std::map< std::size_t, CPUBuffer::Queue > cpuBufferQueuesMap
std::map< std::size_t, ScalarBuffer::Queue > scalarBufferQueuesMap
Queue & _queue
std::unique_ptr< BufferQueuesMaps > _queuesMaps
double badness
ROOT::Math::KahanSum< double > nllSum
ReduceNLLOutput counters
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
static unsigned int total
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void input
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 result
The Kahan summation is a compensated summation algorithm, which significantly reduces numerical error...
Definition Util.h:141
static KahanSum< T, N > Accumulate(Iterator begin, Iterator end, T initialValue=T{})
Iterate over a range and return an instance of a KahanSum.
Definition Util.h:230
const_iterator begin() const
const_iterator end() const
A pseudo container class which is a generator of indices.
Definition TSeq.hxx:67
This class provides a simple interface to execute the same task multiple times in parallel threads,...
unsigned GetPoolSize() const
Returns the number of worker threads in the task arena.
void Foreach(F func, unsigned nTimes, unsigned nChunks=0)
Execute a function without arguments several times in parallel, dividing the execution in nChunks.
const double *__restrict _array
Definition Batches.h:32
Minimal configuration struct to steer the evaluation of a single node with the RooBatchCompute librar...
This class overrides some RooBatchComputeInterface functions, for the purpose of providing a CPU spec...
void compute(Config const &, Computer computer, std::span< double > output, VarSpan vars, ArgSpan extraArgs) override
Compute multiple values using optimized functions.
const std::vector< void(*)(Batches &)> _computeFunctions
double reduceSum(Config const &, InputArr input, size_t n) override
void deleteCudaStream(CudaInterface::CudaStream *) const override
std::unique_ptr< AbsBufferManager > createBufferManager() const override
CudaInterface::CudaStream * newCudaStream() const override
void synchronizeCudaStream(CudaInterface::CudaStream *) const override
Wait until all work that was enqueued on the stream has completed.
ReduceNLLOutput reduceNLL(Config const &, std::span< const double > probas, std::span< const double > weights, std::span< const double > offsetProbas) override
The interface which should be implemented to provide optimised computation functions for implementati...
const Int_t n
Definition legend1.C:16
std::vector< void(*)(Batches &)> getFunctions()
static RooBatchComputeClass computeObj
Static object to trigger the constructor which overwrites the dispatch pointer.
Namespace for dispatching RooFit computations to various backends.
std::span< double > ArgSpan
R__EXTERN RooBatchComputeInterface * dispatchCPU
This dispatch pointer points to an implementation of the compute library, provided one has been loade...
constexpr std::size_t bufferSize
const double *__restrict InputArr
std::span< const std::span< const double > > VarSpan
void probas(TString dataset, TString fin="TMVA.root", Bool_t useTMVAStyle=kTRUE)
static double packFloatIntoNaN(float payload)
Pack float into mantissa of a NaN.
static float unpackNaN(double val)
If val is NaN and a this NaN has been tagged as containing a payload, unpack the float from the manti...