Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
Evaluator.cxx
Go to the documentation of this file.
1/*
2 * Project: RooFit
3 * Authors:
4 * Jonas Rembser, CERN 2021
5 * Emmanouil Michalainas, CERN 2021
6 *
7 * Copyright (c) 2021, CERN
8 *
9 * Redistribution and use in source and binary forms,
10 * with or without modification, are permitted according to the terms
11 * listed in LICENSE (http://roofit.sourceforge.net/license.txt)
12 */
13
14/**
15\file Evaluator.cxx
16\class RooFit::Evaluator
17\ingroup Roofitcore
18
19Evaluates a RooAbsReal object in other ways than recursive graph
20traversal. Currently, it is being used for evaluating a RooAbsReal object and
21supplying the value to the minimizer, during a fit. The class scans the
22dependencies and schedules the computations in a secure and efficient way. The
23computations take place in the RooBatchCompute library and can be carried off
24by either the CPU or a CUDA-supporting GPU. The Evaluator class takes care
25of data transfers. An instance of this class is created every time
26RooAbsPdf::fitTo() is called and gets destroyed when the fitting ends.
27**/
28
29#include <RooFit/Evaluator.h>
30
31#include <RooAbsCategory.h>
32#include <RooAbsData.h>
33#include <RooAbsReal.h>
34#include <RooRealVar.h>
35#include <RooBatchCompute.h>
36#include <RooMsgService.h>
37#include <RooNameReg.h>
38#include <RooSimultaneous.h>
39
40#include <RooBatchCompute.h>
41
43#include "RooFitImplHelpers.h"
44
45#include <atomic>
46#include <iomanip>
47#include <mutex>
48#include <numeric>
49#include <set>
50#include <unordered_set>
51
52namespace RooFit {
53
54namespace {
55
56// To avoid deleted move assignment.
57template <class T>
58void assignSpan(std::span<T> &to, std::span<T> const &from)
59{
60 to = from;
61}
62
63void logArchitectureInfo(bool useGPU)
64{
65 // We have to exit early if the message stream is not active. Otherwise it's
66 // possible that this function skips logging because it thinks it has
67 // already logged, but actually it didn't.
68 if (!RooMsgService::instance().isActive(nullptr, RooFit::Fitting, RooFit::INFO)) {
69 return;
70 }
71
72 // Don't repeat logging architecture info if the useGPU option didn't change
73 {
74 // Second element of pair tracks whether this function has already been called
75 static std::pair<bool, bool> lastUseGPU;
76 if (lastUseGPU.second && lastUseGPU.first == useGPU)
77 return;
78 lastUseGPU = {useGPU, true};
79 }
80
81 auto log = [](std::string_view message) {
82 oocxcoutI(static_cast<RooAbsArg *>(nullptr), Fitting) << message << std::endl;
83 };
84
86 log("using generic CPU library compiled with no vectorizations");
87 } else {
88 log(std::string("using CPU computation library compiled with -m") + RooBatchCompute::cpuArchitectureName());
89 }
90 if (useGPU) {
91 log("using CUDA computation library");
92 }
93}
94
95/// Advise the user to implement CUDA support for a class that had to be
96/// evaluated on the CPU even though the CUDA backend was requested. Just like
97/// for the analogous message about the missing batch evaluation interface in
98/// RooAbsReal::doEval(), the message is only printed once per class, because
99/// the computation graph is evaluated many times, e.g. in every minimizer
100/// iteration.
101void logMissingCudaSupport(RooAbsArg const &arg)
102{
104 return;
105 }
106 static std::set<std::string> warnedClasses;
107 static std::mutex warnedClassesMutex;
108 std::scoped_lock guard{warnedClassesMutex};
109 if (warnedClasses.insert(arg.ClassName()).second) {
110 oocoutI(&arg, FastEvaluations) << "The class " << arg.ClassName()
111 << " could not be evaluated on the GPU because it doesn't support it."
112 << " Consider requesting or implementing it to benefit from a speed up."
113 << std::endl;
114 }
115}
116
117} // namespace
118
119/// A struct used by the Evaluator to store information on the RooAbsArgs in
120/// the computation graph.
121struct NodeInfo {
122
123 bool isScalar() const { return outputSize == 1; }
124
125 RooAbsArg *absArg = nullptr;
127
128 std::shared_ptr<RooBatchCompute::AbsBuffer> buffer;
129 std::size_t iNode = 0;
131 bool fromArrayInput = false;
132 bool isVariable = false;
133 bool isDirty = true;
134 bool isCategory = false;
135 bool hasLogged = false;
136 bool computeInGPU = false;
137 bool isValueServer = false; // if this node is a value server to the top node
138 std::size_t outputSize = 1;
139 std::size_t lastSetValCount = std::numeric_limits<std::size_t>::max();
140 int lastCatVal = std::numeric_limits<int>::max();
141 double scalarBuffer = 0.0;
142 std::vector<NodeInfo *> serverInfos;
143 std::vector<NodeInfo *> clientInfos;
144};
145
146/// Construct a new Evaluator. The constructor analyzes and saves metadata about the graph,
147/// useful for the evaluation of it that will be done later. In case the CUDA mode is selected,
148/// there's also some CUDA-related initialization.
149///
150/// \param[in] absReal The RooAbsReal object that sits on top of the
151/// computation graph that we want to evaluate.
152/// \param[in] useGPU Whether the evaluation should be preferably done on the GPU.
153Evaluator::Evaluator(const RooAbsReal &absReal, bool useGPU)
154 : _topNode{const_cast<RooAbsReal &>(absReal)}, _useGPU{useGPU}
155{
157 if (useGPU && RooBatchCompute::initCUDA() != 0) {
158 throw std::runtime_error("Can't create Evaluator in CUDA mode because RooBatchCompute CUDA could not be loaded!");
159 }
160 // Some checks and logging of used architectures
161 logArchitectureInfo(_useGPU);
162
165
166 RooArgSet serverSet;
168
169 _evalContextCPU.resize(serverSet.size());
170 if (useGPU) {
171 _evalContextCUDA.resize(serverSet.size());
172 }
173
174 std::map<RooFit::Detail::DataKey, NodeInfo *> nodeInfos;
175
176 // Fill the ordered nodes list and initialize the node info structs.
177 _nodes.reserve(serverSet.size());
178 std::size_t iNode = 0;
179 for (RooAbsArg *arg : serverSet) {
180
181 _nodes.emplace_back();
182 auto &nodeInfo = _nodes.back();
183 _nodesMap[arg->namePtr()] = &nodeInfo;
184
185 nodeInfo.absArg = arg;
186 nodeInfo.originalOperMode = arg->operMode();
187 nodeInfo.iNode = iNode;
188 nodeInfos[arg] = &nodeInfo;
189
190 if (dynamic_cast<RooRealVar const *>(arg)) {
191 nodeInfo.isVariable = true;
192 } else {
193 arg->setDataToken(iNode, this);
194 }
195 if (dynamic_cast<RooAbsCategory const *>(arg)) {
196 nodeInfo.isCategory = true;
197 }
198
199 ++iNode;
200 }
201
202 for (NodeInfo &info : _nodes) {
203 info.serverInfos.reserve(info.absArg->servers().size());
204 for (RooAbsArg *server : info.absArg->servers()) {
205 if (server->isValueServer(*info.absArg)) {
206 auto *serverInfo = nodeInfos.at(server);
207 info.serverInfos.emplace_back(serverInfo);
208 serverInfo->clientInfos.emplace_back(&info);
209 }
210 }
211 }
212
213 // Figure out which nodes are value servers to the top node
214 _nodes.back().isValueServer = true; // the top node itself
215 for (auto iter = _nodes.rbegin(); iter != _nodes.rend(); ++iter) {
216 if (!iter->isValueServer)
217 continue;
218 for (auto &serverInfo : iter->serverInfos) {
219 serverInfo->isValueServer = true;
220 }
221 }
222
224
225 if (_useGPU) {
226 // Create the single CUDA stream on which all GPU computations and data
227 // transfers of this Evaluator are enqueued. The graph is evaluated in
228 // topological order, so ordering the operations by the stream is enough
229 // to guarantee correct results.
233 for (auto &info : _nodes) {
234 _evalContextCUDA.setConfig(info.absArg, cfg);
235 }
236 }
237}
238
239/// If there are servers with the same name that got de-duplicated in the
240/// `_nodes` list, we need to set their data tokens too. We find such nodes by
241/// visiting the servers of every known node.
243{
244 for (NodeInfo &info : _nodes) {
245 std::size_t iValueServer = 0;
246 for (RooAbsArg *server : info.absArg->servers()) {
247 if (server->isValueServer(*info.absArg)) {
248 auto *knownServer = info.serverInfos[iValueServer]->absArg;
249 if (knownServer->hasDataToken()) {
250 server->setDataToken(knownServer->dataToken());
251 }
252 ++iValueServer;
253 }
254 }
255 }
256}
257
258void Evaluator::setInput(std::string const &name, std::span<const double> inputArray, bool isOnDevice)
259{
260 if (isOnDevice && !_useGPU) {
261 throw std::runtime_error("Evaluator can only take device array as input in CUDA mode!");
262 }
263
264 // Check if "name" is used in the computation graph. If yes, add the span to
265 // the data map and set the node info accordingly.
266
267 auto found = _nodesMap.find(RooNameReg::ptr(name.c_str()));
268
269 if (found == _nodesMap.end())
270 return;
271
273
274 // Invalidate the caches that reducer nodes key on the input data, like the
275 // cached sum of event weights in RooNLLVarNew. The counter is global so
276 // that generation values can never alias between different Evaluators.
277 {
278 static std::atomic<std::size_t> nextInputGeneration{1};
279 const std::size_t gen = ++nextInputGeneration;
282 }
283
284 NodeInfo &info = *found->second;
285
286 info.fromArrayInput = true;
287 info.absArg->setDataToken(info.iNode);
288 info.outputSize = inputArray.size();
289
290 if (!_useGPU) {
291 _evalContextCPU.set(info.absArg, inputArray);
292 return;
293 }
294
295 if (info.outputSize <= 1) {
296 // Empty or scalar observables from the data don't need to be
297 // copied to the GPU.
298 _evalContextCPU.set(info.absArg, inputArray);
299 _evalContextCUDA.set(info.absArg, inputArray);
300 return;
301 }
302
303 // For simplicity, we put the data on both host and device for
304 // now. This could be optimized by inspecting the clients of the
305 // variable.
306 if (isOnDevice) {
307 _evalContextCUDA.set(info.absArg, inputArray);
308 auto gpuSpan = _evalContextCUDA.at(info.absArg);
309 info.buffer = _bufferManager->makeCpuBuffer(gpuSpan.size());
310 info.buffer->assignFromDevice(gpuSpan);
311 _evalContextCPU.set(info.absArg, {info.buffer->hostReadPtr(), gpuSpan.size()});
312 } else {
313 _evalContextCPU.set(info.absArg, inputArray);
314 auto cpuSpan = _evalContextCPU.at(info.absArg);
315 info.buffer = _bufferManager->makeGpuBuffer(cpuSpan.size());
316 info.buffer->assignFromHost(cpuSpan);
317 _evalContextCUDA.set(info.absArg, {info.buffer->deviceReadPtr(), cpuSpan.size()});
318 }
319}
320
322{
323 std::map<RooFit::Detail::DataKey, std::size_t> sizeMap;
324 for (auto &info : _nodes) {
325 if (info.fromArrayInput) {
326 sizeMap[info.absArg] = info.outputSize;
327 } else {
328 // any buffer for temporary results is invalidated by resetting the output sizes
329 info.buffer.reset();
330 }
331 }
332
333 auto outputSizeMap =
334 RooFit::BatchModeDataHelpers::determineOutputSizes(_topNode, [&](RooFit::Detail::DataKey key) -> int {
335 auto found = sizeMap.find(key);
336 return found != sizeMap.end() ? found->second : -1;
337 });
338
339 for (auto &info : _nodes) {
340 info.outputSize = outputSizeMap.at(info.absArg);
341 info.isDirty = true;
342 }
343
344 if (_useGPU) {
345 markGPUNodes();
346 }
347
349}
350
352{
353 for (auto &info : _nodes) {
354 // Nodes that were deleted before the Evaluator were unregistered
355 if (!info.isVariable && info.absArg) {
356 info.absArg->resetDataToken();
357 }
358 }
359 if (_cudaStream) {
361 }
362}
363
364/// Called from RooAbsArg::~RooAbsArg() when a node of the computation graph is
365/// deleted while this Evaluator is still alive. The Evaluator can't be used
366/// anymore after that, but its destructor must not touch the deleted node.
367/// For internal use only.
369{
370 auto found = _nodesMap.find(arg.namePtr());
371 if (found != _nodesMap.end() && found->second->absArg == &arg) {
372 found->second->absArg = nullptr;
373 }
374}
375
377{
378 using namespace Detail;
379
380 const std::size_t nOut = info.outputSize;
381
382 double *buffer = nullptr;
383 if (nOut == 1) {
384 buffer = &info.scalarBuffer;
385 if (_useGPU) {
386 _evalContextCUDA.set(node, {buffer, nOut});
387 }
388 } else {
389 // Advising to implement the CUDA evaluation makes only sense if the batch was not a scalar.
390 // Otherwise, there would be no speedup benefit.
391 if (!info.hasLogged && _useGPU) {
392 logMissingCudaSupport(*info.absArg);
393 info.hasLogged = true;
394 }
395 if (!info.buffer) {
396 info.buffer = info.copyAfterEvaluation ? _bufferManager->makePinnedBuffer(nOut, _cudaStream)
397 : _bufferManager->makeCpuBuffer(nOut);
398 }
399 buffer = info.buffer->hostWritePtr();
400 }
401 assignSpan(_evalContextCPU._currentOutput, {buffer, nOut});
402 _evalContextCPU.set(node, {buffer, nOut});
403 if (nOut > 1) {
405 }
406 if (info.isCategory) {
407 auto nodeAbsCategory = static_cast<RooAbsCategory const *>(node);
408 if (nOut == 1) {
409 buffer[0] = nodeAbsCategory->getCurrentIndex();
410 } else {
411 throw std::runtime_error("RooFit::Evaluator - non-scalar category values are not supported!");
412 }
413 } else {
414 auto nodeAbsReal = static_cast<RooAbsReal const *>(node);
415 nodeAbsReal->doEval(_evalContextCPU);
416 }
419 if (info.copyAfterEvaluation) {
420 // The deviceReadPtr() call triggers the copy of the result to the GPU.
421 // The copy is ordered by the CUDA stream, so GPU clients enqueued later
422 // will see the result without any further synchronization.
423 _evalContextCUDA.set(node, {info.buffer->deviceReadPtr(), nOut});
424 }
425}
426
427/// Process a variable in the computation graph. This is a separate non-inlined
428/// function such that we can see in performance profiles how long this takes.
430{
431 RooAbsArg *node = nodeInfo.absArg;
432 auto *var = static_cast<RooRealVar const *>(node);
433 if (nodeInfo.lastSetValCount != var->valueResetCounter()) {
434 nodeInfo.lastSetValCount = var->valueResetCounter();
435 for (NodeInfo *clientInfo : nodeInfo.clientInfos) {
436 clientInfo->isDirty = true;
437 }
438 computeCPUNode(node, nodeInfo);
439 nodeInfo.isDirty = false;
440 }
441}
442
443/// Process a category in the computation graph. This is a separate non-inlined
444/// function such that we can see in performance profiles how long this takes.
446{
447 RooAbsArg *node = nodeInfo.absArg;
448 auto *cat = static_cast<RooAbsCategory const *>(node);
449 if (nodeInfo.lastCatVal != cat->getCurrentIndex()) {
450 nodeInfo.lastCatVal = cat->getCurrentIndex();
451 for (NodeInfo *clientInfo : nodeInfo.clientInfos) {
452 clientInfo->isDirty = true;
453 }
454 computeCPUNode(node, nodeInfo);
455 nodeInfo.isDirty = false;
456 }
457}
458
459/// Flags all the clients of a given node dirty. This is a separate non-inlined
460/// function such that we can see in performance profiles how long this takes.
462{
463 for (NodeInfo *clientInfo : nodeInfo.clientInfos) {
464 clientInfo->isDirty = true;
465 }
466}
467
468/// Returns the value of the top node in the computation graph
469std::span<const double> Evaluator::run()
470{
473
475
476 // Discard leftover deferred actions in case a previous evaluation was
477 // aborted by an exception.
480
481 if (_useGPU) {
482 return getValHeterogeneous();
483 }
484
485 for (auto &nodeInfo : _nodes) {
486 if (!nodeInfo.fromArrayInput) {
487 if (nodeInfo.isVariable) {
488 processVariable(nodeInfo);
489 } else if (nodeInfo.isCategory) {
490 processCategory(nodeInfo);
491 } else {
492 if (nodeInfo.isDirty) {
493 setClientsDirty(nodeInfo);
494 computeCPUNode(nodeInfo.absArg, nodeInfo);
495 nodeInfo.isDirty = false;
496 }
497 }
498 }
499 }
500
501 for (auto &action : _evalContextCPU._deferredActions) {
502 action();
503 }
505
506 // return the final output
507 return _evalContextCPU.at(&_topNode);
508}
509
510/// Returns the value of the top node in the computation graph
511std::span<const double> Evaluator::getValHeterogeneous()
512{
513 // Iterate over the nodes in topological order. Like in the CPU-only path
514 // in run(), only dirty nodes are recomputed: a node is dirty if one of its
515 // value servers was recomputed in this evaluation, or if a variable
516 // changed its value since the last evaluation. Clean nodes keep the output
517 // buffers from their last evaluation, which is why the buffers persist
518 // across evaluations in this mode instead of being released as soon as
519 // the clients of a node are computed. During a minimization, most
520 // likelihood evaluations change only one or two parameters (e.g. for the
521 // numeric gradient and the Hessian), so skipping the clean nodes saves
522 // most of the GPU work and the kernel launches. The price is the device
523 // memory for the intermediate results of the whole computation graph,
524 // which is small compared to the input data for the usual graphs where
525 // most nodes are scalars.
526 //
527 // Nodes that are computed on the GPU only enqueue their computation on the
528 // single CUDA stream and return immediately, so independent CPU nodes that
529 // come later in the ordering naturally overlap with the GPU computations.
530 // Ordering by the stream guarantees that GPU nodes see the results of
531 // their GPU servers, and host-side reads of GPU results synchronize on the
532 // stream in the buffer implementation. Since no buffer is recycled within
533 // an evaluation, the results of clean nodes can't be overwritten by
534 // enqueued work either.
535 try {
536 for (auto &info : _nodes) {
537 if (info.fromArrayInput) {
538 continue;
539 }
540 if (info.isVariable) {
541 processVariable(info);
542 } else if (info.isCategory) {
543 processCategory(info);
544 } else if (info.isDirty) {
545 setClientsDirty(info);
546 if (info.computeInGPU) {
547 assignToGPU(info);
548 } else {
549 computeCPUNode(info.absArg, info);
550 }
551 info.isDirty = false;
552 }
553 }
554 } catch (...) {
555 // The evaluation was aborted, but readbacks that compute() calls
556 // deferred may still be armed. Deliver them now, while the destination
557 // memory in the nodes of the computation graph is guaranteed to be
558 // alive, so that no armed readback survives into a later evaluation.
559 // The node that threw stays dirty and gets recomputed in the next
560 // evaluation, together with its clients that were already flagged.
561 try {
563 } catch (...) {
564 // The stream is in an unrecoverable error state. The deferred
565 // readbacks are dropped together with the scratch memory when the
566 // stream gets deleted.
567 }
570 throw;
571 }
572
573 // Ensure that all enqueued GPU work has completed when run() returns. For
574 // the usual likelihood evaluations this is mostly a no-op, because the
575 // final reduction has synchronized the stream already. It also delivers
576 // the deferred readbacks like the evaluation error counters.
578
579 // Run the deferred actions now that all results have arrived on the host,
580 // e.g. the logging of evaluation errors that were counted on the GPU.
581 // Nodes evaluated on the CPU register their actions in the CPU context,
582 // so both contexts are drained.
583 for (auto *ctx : {&_evalContextCUDA, &_evalContextCPU}) {
584 for (auto &action : ctx->_deferredActions) {
585 action();
586 }
587 ctx->_deferredActions.clear();
588 }
589
590 // return the final value
592}
593
594/// Enqueue the computation of a node on the GPU.
596{
597 using namespace Detail;
598
599 auto node = static_cast<RooAbsReal const *>(info.absArg);
600
601 const std::size_t nOut = info.outputSize;
602
603 double *buffer = nullptr;
604 if (nOut == 1) {
605 buffer = &info.scalarBuffer;
606 _evalContextCPU.set(node, {buffer, nOut});
607 } else {
608 if (!info.buffer) {
609 info.buffer = info.copyAfterEvaluation ? _bufferManager->makePinnedBuffer(nOut, _cudaStream)
610 : _bufferManager->makeGpuBuffer(nOut);
611 }
612 buffer = info.buffer->deviceWritePtr();
613 }
614 assignSpan(_evalContextCUDA._currentOutput, {buffer, nOut});
615 _evalContextCUDA.set(node, {buffer, nOut});
616 node->doEval(_evalContextCUDA);
617 if (info.copyAfterEvaluation) {
618 // The hostReadPtr() call triggers the copy of the result to the host,
619 // which waits for the enqueued computation via the CUDA stream.
620 _evalContextCPU.set(node, {info.buffer->hostReadPtr(), nOut});
621 }
622}
623
624/// Decides which nodes are assigned to the GPU in a CUDA fit.
626{
627 // Decide which nodes get evaluated on the GPU: we select nodes that support
628 // CUDA evaluation and have at least one input of size greater than one.
629 for (auto &info : _nodes) {
630 info.computeInGPU = false;
631 if (!info.absArg->canComputeBatchWithCuda()) {
632 continue;
633 }
634 for (NodeInfo const *serverInfo : info.serverInfos) {
635 if (serverInfo->outputSize > 1) {
636 info.computeInGPU = true;
637 break;
638 }
639 }
640 }
641
642 // In a second pass, figure out which nodes need to copy over their results.
643 for (auto &info : _nodes) {
644 info.copyAfterEvaluation = false;
645 // scalar nodes don't need copying
646 if (!info.isScalar()) {
647 for (auto *clientInfo : info.clientInfos) {
648 if (info.computeInGPU != clientInfo->computeInGPU) {
649 info.copyAfterEvaluation = true;
650 break;
651 }
652 }
653 }
654 }
655}
656
657/// \brief Sets the number of threads to use for the evaluation of a single node.
658///
659/// With a value greater than one, the computation functions and reductions of
660/// the CPU backend process large batches multi-threaded, using up to the
661/// given number of threads. Nodes evaluated on the CPU with fewer events than
662/// an internal threshold are still evaluated single-threaded, so requesting
663/// multiple threads never introduces scheduling overhead for small fits.
664void Evaluator::setNThreads(int nThreads)
665{
666 for (auto &info : _nodes) {
667 if (info.isVariable) {
668 continue;
669 }
671 cfg.setNThreads(nThreads);
672 _evalContextCPU.setConfig(info.absArg, cfg);
673 }
674}
675
676/// Temporarily change the operation mode of a RooAbsArg until the
677/// Evaluator gets deleted.
679{
680 if (!_operModeChanges)
681 _operModeChanges = std::make_unique<ChangeOperModeRAII>();
682 _operModeChanges->change(arg, opMode);
683}
684
685// Change the operation modes of all RooAbsArgs in the computation graph.
686// The changes are reset when the returned RAII object goes out of scope.
687//
688// We also walk transitively through value clients of the nodes to cover any
689// node that RooAbsReal::doEval (the fallback scalar implementation) might
690// inadvertently propagate the ADirty mode to via its recursive restore: that
691// helper sets servers temporarily to AClean and then calls
692// setOperMode(oldOperMode) to restore, which recurses to value clients when
693// oldOperMode is ADirty. If we did not protect those clients here, any node
694// outside the computation graph that shares a fundamental (e.g. a parameter
695// like a RooRealVar) would be left permanently in ADirty after the first
696// minimization, dramatically slowing down later scalar evaluations (for
697// example on pdfs held by the legacy test statistics' internal cache).
698std::unique_ptr<ChangeOperModeRAII> Evaluator::setOperModes(RooAbsArg::OperMode opMode)
699{
700 auto out = std::make_unique<ChangeOperModeRAII>();
701 std::unordered_set<RooAbsArg *> visited;
702
703 std::vector<RooAbsArg *> queue;
704 queue.reserve(_nodes.size());
705 for (auto &info : _nodes) {
706 queue.push_back(info.absArg);
707 }
708
709 while (!queue.empty()) {
710 RooAbsArg *node = queue.back();
711 queue.pop_back();
712 if (!visited.insert(node).second)
713 continue;
714
715 out->change(node, opMode);
716
717 // Only follow value-client links: that is exactly the propagation path
718 // used by RooAbsArg::setOperMode with mode==ADirty.
719 if (opMode == RooAbsArg::ADirty) {
720 for (auto *client : node->valueClients()) {
721 queue.push_back(client);
722 }
723 }
724 }
725 return out;
726}
727
728void Evaluator::print(std::ostream &os)
729{
730 std::cout << "--- RooFit BatchMode evaluation ---\n";
731
732 std::vector<int> widths{9, 37, 20, 9, 10, 20};
733
734 auto printElement = [&](int iCol, auto const &t) {
735 const char separator = ' ';
736 os << separator << std::left << std::setw(widths[iCol]) << std::setfill(separator) << t;
737 os << "|";
738 };
739
740 auto printHorizontalRow = [&]() {
741 int n = 0;
742 for (int w : widths) {
743 n += w + 2;
744 }
745 for (int i = 0; i < n; i++) {
746 os << '-';
747 }
748 os << "|\n";
749 };
750
751 printHorizontalRow();
752
753 os << "|";
754 printElement(0, "Index");
755 printElement(1, "Name");
756 printElement(2, "Class");
757 printElement(3, "Size");
758 printElement(4, "From Data");
759 printElement(5, "1st value");
760 std::cout << "\n";
761
762 printHorizontalRow();
763
764 for (std::size_t iNode = 0; iNode < _nodes.size(); ++iNode) {
765 auto &nodeInfo = _nodes[iNode];
766 RooAbsArg *node = nodeInfo.absArg;
767
768 auto span = _evalContextCPU.at(node);
769
770 os << "|";
771 printElement(0, iNode);
772 printElement(1, node->GetName());
773 printElement(2, node->ClassName());
774 printElement(3, nodeInfo.outputSize);
775 printElement(4, nodeInfo.fromArrayInput);
776 printElement(5, span[0]);
777
778 std::cout << "\n";
779 }
780
781 printHorizontalRow();
782}
783
784/// Gets all the parameters of the RooAbsReal. This is in principle not
785/// necessary, because we can always ask the RooAbsReal itself, but the
786/// Evaluator has the cached information to get the answer quicker.
787/// Therefore, this is not meant to be used in general, just where it matters.
788/// \warning If we find another solution to get the parameters efficiently,
789/// this function might be removed without notice.
791{
792 RooArgSet parameters;
793 for (auto &nodeInfo : _nodes) {
794 if (nodeInfo.isValueServer && nodeInfo.absArg->isFundamental()) {
795 parameters.add(*nodeInfo.absArg);
796 }
797 }
798 // Just like in RooAbsArg::getParameters(), we sort the parameters alphabetically.
799 parameters.sort();
800 return parameters;
801}
802
803/// \brief Sets the offset mode for evaluation.
804///
805/// This function sets the offset mode for evaluation to the specified mode.
806/// It updates the offset mode for both CPU and CUDA evaluation contexts.
807///
808/// \param mode The offset mode to be set.
809///
810/// \note This function marks reducer nodes as dirty if the offset mode is
811/// changed, because only reducer nodes can use offsetting.
813{
815 return;
816
819
820 for (auto &nodeInfo : _nodes) {
821 if (nodeInfo.absArg->isReducerNode()) {
822 nodeInfo.isDirty = true;
823 }
824 }
825}
826
827} // namespace RooFit
#define oocoutI(o, a)
#define oocxcoutI(o, a)
Option_t Option_t TPoint TPoint const char mode
char name[80]
Definition TGX11.cxx:142
Common abstract base class for objects that represent a value and a "shape" in RooFit.
Definition RooAbsArg.h:77
const TNamed * namePtr() const
De-duplicated pointer to this object's name.
Definition RooAbsArg.h:483
const RefCountList_t & valueClients() const
List of all value clients of this object. Value clients receive value updates.
Definition RooAbsArg.h:140
void setDataToken(std::size_t index, RooFit::Evaluator *evaluator=nullptr)
Sets the token for retrieving results in the BatchMode.
OperMode operMode() const
Query the operation mode of this node.
Definition RooAbsArg.h:399
A space to attach TBranches.
virtual bool add(const RooAbsArg &var, bool silent=false)
Add the specified argument to list.
Storage_t::size_type size() const
void sort(bool reverse=false)
Sort collection using std::sort and name comparison.
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:63
virtual void doEval(RooFit::EvalContext &) const
Base function for computing multiple values of a RooAbsReal.
RooArgSet is a container object that can hold multiple RooAbsArg objects.
Definition RooArgSet.h:24
Minimal configuration struct to steer the evaluation of a single node with the RooBatchCompute librar...
void setCudaStream(CudaInterface::CudaStream *cudaStream)
void setNThreads(int nThreads)
Number of threads to use for CPU batch computations and reductions.
virtual void synchronizeCudaStream(CudaInterface::CudaStream *) const =0
Wait until all work that was enqueued on the stream has completed.
virtual std::unique_ptr< AbsBufferManager > createBufferManager() const =0
virtual CudaInterface::CudaStream * newCudaStream() const =0
virtual void deleteCudaStream(CudaInterface::CudaStream *) const =0
std::size_t _inputGeneration
std::vector< std::function< void()> > _deferredActions
void set(RooAbsArg const *arg, std::span< const double > const &span)
Definition EvalContext.h:92
std::span< const double > at(RooAbsArg const *arg, RooAbsArg const *caller=nullptr)
void enableVectorBuffers(bool enable)
OffsetMode _offsetMode
RooBatchCompute::Config config(RooAbsArg const *arg) const
void setConfig(RooAbsArg const *arg, RooBatchCompute::Config const &config)
std::span< double > _currentOutput
void resize(std::size_t n)
void print(std::ostream &os)
void setClientsDirty(NodeInfo &nodeInfo)
Flags all the clients of a given node dirty.
std::unique_ptr< ChangeOperModeRAII > setOperModes(RooAbsArg::OperMode opMode)
RooArgSet getParameters() const
Gets all the parameters of the RooAbsReal.
void setOffsetMode(RooFit::EvalContext::OffsetMode)
Sets the offset mode for evaluation.
void unregisterNode(RooAbsArg const &arg)
Called from RooAbsArg::~RooAbsArg() when a node of the computation graph is deleted while this Evalua...
void syncDataTokens()
If there are servers with the same name that got de-duplicated in the _nodes list,...
const bool _useGPU
Definition Evaluator.h:70
std::unordered_map< TNamed const *, NodeInfo * > _nodesMap
Definition Evaluator.h:76
std::unique_ptr< ChangeOperModeRAII > _operModeChanges
Definition Evaluator.h:77
std::vector< NodeInfo > _nodes
Definition Evaluator.h:75
bool _needToUpdateOutputSizes
Definition Evaluator.h:72
std::span< const double > getValHeterogeneous()
Returns the value of the top node in the computation graph.
std::span< const double > run()
Returns the value of the top node in the computation graph.
Evaluator(const RooAbsReal &absReal, bool useGPU=false)
Construct a new Evaluator.
void setNThreads(int nThreads)
Sets the number of threads to use for the evaluation of a single node.
void processVariable(NodeInfo &nodeInfo)
Process a variable in the computation graph.
void processCategory(NodeInfo &nodeInfo)
Process a category in the computation graph.
RooBatchCompute::CudaInterface::CudaStream * _cudaStream
Definition Evaluator.h:79
std::unique_ptr< RooBatchCompute::AbsBufferManager > _bufferManager
Definition Evaluator.h:68
void markGPUNodes()
Decides which nodes are assigned to the GPU in a CUDA fit.
void assignToGPU(NodeInfo &info)
Enqueue the computation of a node on the GPU.
void setInput(std::string const &name, std::span< const double > inputArray, bool isOnDevice)
RooFit::EvalContext _evalContextCUDA
Definition Evaluator.h:74
RooFit::EvalContext _evalContextCPU
Definition Evaluator.h:73
void computeCPUNode(const RooAbsArg *node, NodeInfo &info)
void setOperMode(RooAbsArg *arg, RooAbsArg::OperMode opMode)
Temporarily change the operation mode of a RooAbsArg until the Evaluator gets deleted.
RooAbsReal & _topNode
Definition Evaluator.h:69
static RooMsgService & instance()
Return reference to singleton instance.
static const TNamed * ptr(const char *stringPtr)
Return a unique TNamed pointer for given C++ string.
Variable that can be changed from the outside.
Definition RooRealVar.h:37
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
virtual const char * ClassName() const
Returns name of class to which the object belongs.
Definition TObject.cxx:226
RVec< PromoteType< T > > log(const RVec< T > &v)
Definition RVec.hxx:1821
const Int_t n
Definition legend1.C:16
R__EXTERN RooBatchComputeInterface * dispatchCUDA
std::string cpuArchitectureName()
R__EXTERN RooBatchComputeInterface * dispatchCPU
This dispatch pointer points to an implementation of the compute library, provided one has been loade...
Architecture cpuArchitecture()
int initCPU()
Inspect hardware capabilities, and load the optimal library for RooFit computations.
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
Definition CodegenImpl.h:73
@ FastEvaluations
void getSortedComputationGraph(RooAbsArg const &func, RooArgSet &out)
A struct used by the Evaluator to store information on the RooAbsArgs in the computation graph.
RooAbsArg * absArg
bool isScalar() const
std::size_t iNode
std::size_t lastSetValCount
std::vector< NodeInfo * > serverInfos
RooAbsArg::OperMode originalOperMode
std::size_t outputSize
std::vector< NodeInfo * > clientInfos
std::shared_ptr< RooBatchCompute::AbsBuffer > buffer