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);
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 if (!info.isVariable) {
355 info.absArg->resetDataToken();
356 }
357 }
358 if (_cudaStream) {
360 }
361}
362
364{
365 using namespace Detail;
366
367 const std::size_t nOut = info.outputSize;
368
369 double *buffer = nullptr;
370 if (nOut == 1) {
371 buffer = &info.scalarBuffer;
372 if (_useGPU) {
373 _evalContextCUDA.set(node, {buffer, nOut});
374 }
375 } else {
376 // Advising to implement the CUDA evaluation makes only sense if the batch was not a scalar.
377 // Otherwise, there would be no speedup benefit.
378 if (!info.hasLogged && _useGPU) {
379 logMissingCudaSupport(*info.absArg);
380 info.hasLogged = true;
381 }
382 if (!info.buffer) {
383 info.buffer = info.copyAfterEvaluation ? _bufferManager->makePinnedBuffer(nOut, _cudaStream)
384 : _bufferManager->makeCpuBuffer(nOut);
385 }
386 buffer = info.buffer->hostWritePtr();
387 }
388 assignSpan(_evalContextCPU._currentOutput, {buffer, nOut});
389 _evalContextCPU.set(node, {buffer, nOut});
390 if (nOut > 1) {
392 }
393 if (info.isCategory) {
394 auto nodeAbsCategory = static_cast<RooAbsCategory const *>(node);
395 if (nOut == 1) {
396 buffer[0] = nodeAbsCategory->getCurrentIndex();
397 } else {
398 throw std::runtime_error("RooFit::Evaluator - non-scalar category values are not supported!");
399 }
400 } else {
401 auto nodeAbsReal = static_cast<RooAbsReal const *>(node);
402 nodeAbsReal->doEval(_evalContextCPU);
403 }
406 if (info.copyAfterEvaluation) {
407 // The deviceReadPtr() call triggers the copy of the result to the GPU.
408 // The copy is ordered by the CUDA stream, so GPU clients enqueued later
409 // will see the result without any further synchronization.
410 _evalContextCUDA.set(node, {info.buffer->deviceReadPtr(), nOut});
411 }
412}
413
414/// Process a variable in the computation graph. This is a separate non-inlined
415/// function such that we can see in performance profiles how long this takes.
417{
418 RooAbsArg *node = nodeInfo.absArg;
419 auto *var = static_cast<RooRealVar const *>(node);
420 if (nodeInfo.lastSetValCount != var->valueResetCounter()) {
421 nodeInfo.lastSetValCount = var->valueResetCounter();
422 for (NodeInfo *clientInfo : nodeInfo.clientInfos) {
423 clientInfo->isDirty = true;
424 }
425 computeCPUNode(node, nodeInfo);
426 nodeInfo.isDirty = false;
427 }
428}
429
430/// Process a category in the computation graph. This is a separate non-inlined
431/// function such that we can see in performance profiles how long this takes.
433{
434 RooAbsArg *node = nodeInfo.absArg;
435 auto *cat = static_cast<RooAbsCategory const *>(node);
436 if (nodeInfo.lastCatVal != cat->getCurrentIndex()) {
437 nodeInfo.lastCatVal = cat->getCurrentIndex();
438 for (NodeInfo *clientInfo : nodeInfo.clientInfos) {
439 clientInfo->isDirty = true;
440 }
441 computeCPUNode(node, nodeInfo);
442 nodeInfo.isDirty = false;
443 }
444}
445
446/// Flags all the clients of a given node dirty. This is a separate non-inlined
447/// function such that we can see in performance profiles how long this takes.
449{
450 for (NodeInfo *clientInfo : nodeInfo.clientInfos) {
451 clientInfo->isDirty = true;
452 }
453}
454
455/// Returns the value of the top node in the computation graph
456std::span<const double> Evaluator::run()
457{
460
462
463 // Discard leftover deferred actions in case a previous evaluation was
464 // aborted by an exception.
467
468 if (_useGPU) {
469 return getValHeterogeneous();
470 }
471
472 for (auto &nodeInfo : _nodes) {
473 if (!nodeInfo.fromArrayInput) {
474 if (nodeInfo.isVariable) {
475 processVariable(nodeInfo);
476 } else if (nodeInfo.isCategory) {
477 processCategory(nodeInfo);
478 } else {
479 if (nodeInfo.isDirty) {
480 setClientsDirty(nodeInfo);
481 computeCPUNode(nodeInfo.absArg, nodeInfo);
482 nodeInfo.isDirty = false;
483 }
484 }
485 }
486 }
487
488 for (auto &action : _evalContextCPU._deferredActions) {
489 action();
490 }
492
493 // return the final output
494 return _evalContextCPU.at(&_topNode);
495}
496
497/// Returns the value of the top node in the computation graph
498std::span<const double> Evaluator::getValHeterogeneous()
499{
500 // Iterate over the nodes in topological order. Like in the CPU-only path
501 // in run(), only dirty nodes are recomputed: a node is dirty if one of its
502 // value servers was recomputed in this evaluation, or if a variable
503 // changed its value since the last evaluation. Clean nodes keep the output
504 // buffers from their last evaluation, which is why the buffers persist
505 // across evaluations in this mode instead of being released as soon as
506 // the clients of a node are computed. During a minimization, most
507 // likelihood evaluations change only one or two parameters (e.g. for the
508 // numeric gradient and the Hessian), so skipping the clean nodes saves
509 // most of the GPU work and the kernel launches. The price is the device
510 // memory for the intermediate results of the whole computation graph,
511 // which is small compared to the input data for the usual graphs where
512 // most nodes are scalars.
513 //
514 // Nodes that are computed on the GPU only enqueue their computation on the
515 // single CUDA stream and return immediately, so independent CPU nodes that
516 // come later in the ordering naturally overlap with the GPU computations.
517 // Ordering by the stream guarantees that GPU nodes see the results of
518 // their GPU servers, and host-side reads of GPU results synchronize on the
519 // stream in the buffer implementation. Since no buffer is recycled within
520 // an evaluation, the results of clean nodes can't be overwritten by
521 // enqueued work either.
522 try {
523 for (auto &info : _nodes) {
524 if (info.fromArrayInput) {
525 continue;
526 }
527 if (info.isVariable) {
528 processVariable(info);
529 } else if (info.isCategory) {
530 processCategory(info);
531 } else if (info.isDirty) {
532 setClientsDirty(info);
533 if (info.computeInGPU) {
534 assignToGPU(info);
535 } else {
536 computeCPUNode(info.absArg, info);
537 }
538 info.isDirty = false;
539 }
540 }
541 } catch (...) {
542 // The evaluation was aborted, but readbacks that compute() calls
543 // deferred may still be armed. Deliver them now, while the destination
544 // memory in the nodes of the computation graph is guaranteed to be
545 // alive, so that no armed readback survives into a later evaluation.
546 // The node that threw stays dirty and gets recomputed in the next
547 // evaluation, together with its clients that were already flagged.
548 try {
550 } catch (...) {
551 // The stream is in an unrecoverable error state. The deferred
552 // readbacks are dropped together with the scratch memory when the
553 // stream gets deleted.
554 }
557 throw;
558 }
559
560 // Ensure that all enqueued GPU work has completed when run() returns. For
561 // the usual likelihood evaluations this is mostly a no-op, because the
562 // final reduction has synchronized the stream already. It also delivers
563 // the deferred readbacks like the evaluation error counters.
565
566 // Run the deferred actions now that all results have arrived on the host,
567 // e.g. the logging of evaluation errors that were counted on the GPU.
568 // Nodes evaluated on the CPU register their actions in the CPU context,
569 // so both contexts are drained.
570 for (auto *ctx : {&_evalContextCUDA, &_evalContextCPU}) {
571 for (auto &action : ctx->_deferredActions) {
572 action();
573 }
574 ctx->_deferredActions.clear();
575 }
576
577 // return the final value
579}
580
581/// Enqueue the computation of a node on the GPU.
583{
584 using namespace Detail;
585
586 auto node = static_cast<RooAbsReal const *>(info.absArg);
587
588 const std::size_t nOut = info.outputSize;
589
590 double *buffer = nullptr;
591 if (nOut == 1) {
592 buffer = &info.scalarBuffer;
593 _evalContextCPU.set(node, {buffer, nOut});
594 } else {
595 if (!info.buffer) {
596 info.buffer = info.copyAfterEvaluation ? _bufferManager->makePinnedBuffer(nOut, _cudaStream)
597 : _bufferManager->makeGpuBuffer(nOut);
598 }
599 buffer = info.buffer->deviceWritePtr();
600 }
601 assignSpan(_evalContextCUDA._currentOutput, {buffer, nOut});
602 _evalContextCUDA.set(node, {buffer, nOut});
603 node->doEval(_evalContextCUDA);
604 if (info.copyAfterEvaluation) {
605 // The hostReadPtr() call triggers the copy of the result to the host,
606 // which waits for the enqueued computation via the CUDA stream.
607 _evalContextCPU.set(node, {info.buffer->hostReadPtr(), nOut});
608 }
609}
610
611/// Decides which nodes are assigned to the GPU in a CUDA fit.
613{
614 // Decide which nodes get evaluated on the GPU: we select nodes that support
615 // CUDA evaluation and have at least one input of size greater than one.
616 for (auto &info : _nodes) {
617 info.computeInGPU = false;
618 if (!info.absArg->canComputeBatchWithCuda()) {
619 continue;
620 }
621 for (NodeInfo const *serverInfo : info.serverInfos) {
622 if (serverInfo->outputSize > 1) {
623 info.computeInGPU = true;
624 break;
625 }
626 }
627 }
628
629 // In a second pass, figure out which nodes need to copy over their results.
630 for (auto &info : _nodes) {
631 info.copyAfterEvaluation = false;
632 // scalar nodes don't need copying
633 if (!info.isScalar()) {
634 for (auto *clientInfo : info.clientInfos) {
635 if (info.computeInGPU != clientInfo->computeInGPU) {
636 info.copyAfterEvaluation = true;
637 break;
638 }
639 }
640 }
641 }
642}
643
644/// \brief Sets the number of threads to use for the evaluation of a single node.
645///
646/// With a value greater than one, the computation functions and reductions of
647/// the CPU backend process large batches multi-threaded, using up to the
648/// given number of threads. Nodes evaluated on the CPU with fewer events than
649/// an internal threshold are still evaluated single-threaded, so requesting
650/// multiple threads never introduces scheduling overhead for small fits.
651void Evaluator::setNThreads(int nThreads)
652{
653 for (auto &info : _nodes) {
654 if (info.isVariable) {
655 continue;
656 }
658 cfg.setNThreads(nThreads);
659 _evalContextCPU.setConfig(info.absArg, cfg);
660 }
661}
662
663/// Temporarily change the operation mode of a RooAbsArg until the
664/// Evaluator gets deleted.
666{
667 if (!_operModeChanges)
668 _operModeChanges = std::make_unique<ChangeOperModeRAII>();
669 _operModeChanges->change(arg, opMode);
670}
671
672// Change the operation modes of all RooAbsArgs in the computation graph.
673// The changes are reset when the returned RAII object goes out of scope.
674//
675// We also walk transitively through value clients of the nodes to cover any
676// node that RooAbsReal::doEval (the fallback scalar implementation) might
677// inadvertently propagate the ADirty mode to via its recursive restore: that
678// helper sets servers temporarily to AClean and then calls
679// setOperMode(oldOperMode) to restore, which recurses to value clients when
680// oldOperMode is ADirty. If we did not protect those clients here, any node
681// outside the computation graph that shares a fundamental (e.g. a parameter
682// like a RooRealVar) would be left permanently in ADirty after the first
683// minimization, dramatically slowing down later scalar evaluations (for
684// example on pdfs held by the legacy test statistics' internal cache).
685std::unique_ptr<ChangeOperModeRAII> Evaluator::setOperModes(RooAbsArg::OperMode opMode)
686{
687 auto out = std::make_unique<ChangeOperModeRAII>();
688 std::unordered_set<RooAbsArg *> visited;
689
690 std::vector<RooAbsArg *> queue;
691 queue.reserve(_nodes.size());
692 for (auto &info : _nodes) {
693 queue.push_back(info.absArg);
694 }
695
696 while (!queue.empty()) {
697 RooAbsArg *node = queue.back();
698 queue.pop_back();
699 if (!visited.insert(node).second)
700 continue;
701
702 out->change(node, opMode);
703
704 // Only follow value-client links: that is exactly the propagation path
705 // used by RooAbsArg::setOperMode with mode==ADirty.
706 if (opMode == RooAbsArg::ADirty) {
707 for (auto *client : node->valueClients()) {
708 queue.push_back(client);
709 }
710 }
711 }
712 return out;
713}
714
715void Evaluator::print(std::ostream &os)
716{
717 std::cout << "--- RooFit BatchMode evaluation ---\n";
718
719 std::vector<int> widths{9, 37, 20, 9, 10, 20};
720
721 auto printElement = [&](int iCol, auto const &t) {
722 const char separator = ' ';
723 os << separator << std::left << std::setw(widths[iCol]) << std::setfill(separator) << t;
724 os << "|";
725 };
726
727 auto printHorizontalRow = [&]() {
728 int n = 0;
729 for (int w : widths) {
730 n += w + 2;
731 }
732 for (int i = 0; i < n; i++) {
733 os << '-';
734 }
735 os << "|\n";
736 };
737
738 printHorizontalRow();
739
740 os << "|";
741 printElement(0, "Index");
742 printElement(1, "Name");
743 printElement(2, "Class");
744 printElement(3, "Size");
745 printElement(4, "From Data");
746 printElement(5, "1st value");
747 std::cout << "\n";
748
749 printHorizontalRow();
750
751 for (std::size_t iNode = 0; iNode < _nodes.size(); ++iNode) {
752 auto &nodeInfo = _nodes[iNode];
753 RooAbsArg *node = nodeInfo.absArg;
754
755 auto span = _evalContextCPU.at(node);
756
757 os << "|";
758 printElement(0, iNode);
759 printElement(1, node->GetName());
760 printElement(2, node->ClassName());
761 printElement(3, nodeInfo.outputSize);
762 printElement(4, nodeInfo.fromArrayInput);
763 printElement(5, span[0]);
764
765 std::cout << "\n";
766 }
767
768 printHorizontalRow();
769}
770
771/// Gets all the parameters of the RooAbsReal. This is in principle not
772/// necessary, because we can always ask the RooAbsReal itself, but the
773/// Evaluator has the cached information to get the answer quicker.
774/// Therefore, this is not meant to be used in general, just where it matters.
775/// \warning If we find another solution to get the parameters efficiently,
776/// this function might be removed without notice.
778{
779 RooArgSet parameters;
780 for (auto &nodeInfo : _nodes) {
781 if (nodeInfo.isValueServer && nodeInfo.absArg->isFundamental()) {
782 parameters.add(*nodeInfo.absArg);
783 }
784 }
785 // Just like in RooAbsArg::getParameters(), we sort the parameters alphabetically.
786 parameters.sort();
787 return parameters;
788}
789
790/// \brief Sets the offset mode for evaluation.
791///
792/// This function sets the offset mode for evaluation to the specified mode.
793/// It updates the offset mode for both CPU and CUDA evaluation contexts.
794///
795/// \param mode The offset mode to be set.
796///
797/// \note This function marks reducer nodes as dirty if the offset mode is
798/// changed, because only reducer nodes can use offsetting.
800{
802 return;
803
806
807 for (auto &nodeInfo : _nodes) {
808 if (nodeInfo.absArg->isReducerNode()) {
809 nodeInfo.isDirty = true;
810 }
811 }
812}
813
814} // 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:76
const TNamed * namePtr() const
De-duplicated pointer to this object's name.
Definition RooAbsArg.h:482
void setDataToken(std::size_t index)
Sets the token for retrieving results in the BatchMode. For internal use only.
const RefCountList_t & valueClients() const
List of all value clients of this object. Value clients receive value updates.
Definition RooAbsArg.h:139
OperMode operMode() const
Query the operation mode of this node.
Definition RooAbsArg.h:398
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 syncDataTokens()
If there are servers with the same name that got de-duplicated in the _nodes list,...
const bool _useGPU
Definition Evaluator.h:68
std::unordered_map< TNamed const *, NodeInfo * > _nodesMap
Definition Evaluator.h:74
std::unique_ptr< ChangeOperModeRAII > _operModeChanges
Definition Evaluator.h:75
std::vector< NodeInfo > _nodes
Definition Evaluator.h:73
bool _needToUpdateOutputSizes
Definition Evaluator.h:70
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:77
std::unique_ptr< RooBatchCompute::AbsBufferManager > _bufferManager
Definition Evaluator.h:66
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:72
RooFit::EvalContext _evalContextCPU
Definition Evaluator.h:71
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:67
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