Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RooBatchCompute.cu
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.cu
15\class RbcClass
16\ingroup roofit_dev_docs_batchcompute
17
18This file contains the code for cuda computations using the RooBatchCompute library.
19**/
20
21#include "RooBatchCompute.h"
22#include "RooNaNPacker.h"
23#include "Batches.h"
24#include "CudaInterface.h"
25
26#include <algorithm>
27#include <array>
28#include <cassert>
29#include <cstring>
30#include <functional>
31#include <map>
32#include <queue>
33#include <unordered_map>
34#include <vector>
35
36namespace RooBatchCompute {
37namespace CUDA {
38
39constexpr int blockSize = 512;
40
41namespace {
42
43void fillBatches(Batches &batches, double *output, size_t nEvents, std::size_t nBatches, std::size_t nExtraArgs)
44{
45 batches.nEvents = nEvents;
46 batches.nBatches = nBatches;
47 batches.nExtra = nExtraArgs;
48 batches.output = output;
49}
50
51void fillArrays(Batch *arrays, VarSpan vars, double *buffer, double *bufferDevice, std::size_t nEvents)
52{
53 for (int i = 0; i < vars.size(); i++) {
54 const std::span<const double> &span = vars[i];
55 arrays[i]._isVector = span.empty() || span.size() >= nEvents;
56 if (!arrays[i]._isVector) {
57 // In the scalar case, the value is not on the GPU yet, so we have to
58 // copy the value to the GPU buffer.
59 buffer[i] = span[0];
60 arrays[i]._array = bufferDevice + i;
61 } else {
62 // In the vector input cases, they are already on the GPU, so we can
63 // fill be buffer with some dummy value and set the input span
64 // directly.
65 buffer[i] = 0.0;
66 arrays[i]._array = span.data();
67 }
68 }
69}
70
71int getGridSize(std::size_t n)
72{
73 // The grid size should be not larger than the order of number of streaming
74 // multiprocessors (SMs) in an Nvidia GPU. The number 84 was chosen because
75 // the developers were using an Nvidia RTX A4500, which has 46 SMs. This was
76 // multiplied by a factor of 1.5, as recommended by stackoverflow.
77 //
78 // But when there are not enough elements to load the GPU, the number should
79 // be lower: that's why there is the std::ceil().
80 //
81 // Note: for grid sizes larger than 512, the Kahan summation kernels give
82 // wrong results. This problem is not understood, but also not really worth
83 // investigating further, as that number is unreasonably large anyway.
84 constexpr int maxGridSize = 84;
85 return std::min(int(std::ceil(double(n) / blockSize)), maxGridSize);
86}
87
88/// Scratch memory attached to a CUDA stream, used for staging small
89/// per-kernel-launch data like the Batches descriptor and reduction results.
90///
91/// The slots form a ring: acquire() returns the next slot, waiting for the
92/// completion of the work that was previously enqueued from that slot if it
93/// is still in flight (which is rare, given the depth of the ring). Each slot
94/// pairs a pinned host buffer with a device buffer of the same capacity, so
95/// staging copies are truly asynchronous and no cudaMalloc()/cudaFree() calls
96/// happen in the evaluation hot loop.
97///
98/// Like the rest of the RooBatchCompute library, this class is not
99/// thread-safe: RooFit evaluates on a single thread per process.
100class StreamScratch {
101public:
102 struct Slot {
103 char *host = nullptr; // pinned host memory
104 char *device = nullptr;
105 std::size_t capacity = 0;
106 cudaEvent_t event = nullptr; // recorded after the last enqueued use
107 bool inFlight = false;
108 };
109
110 StreamScratch() = default;
111 StreamScratch(StreamScratch const &) = delete;
112 StreamScratch &operator=(StreamScratch const &) = delete;
113
114 Slot &acquire(std::size_t n)
115 {
116 Slot &slot = _slots[_next];
117 _next = (_next + 1) % _slots.size();
118 if (slot.inFlight) {
120 slot.inFlight = false;
121 }
122 if (slot.capacity < n) {
123 // Reset the slot state before reallocating, so that a throwing
124 // allocation can't leave dangling pointers with a stale capacity
125 // behind (which would lead to a double free later).
126 if (slot.host) {
128 slot.host = nullptr;
129 }
130 if (slot.device) {
131 ERRCHECK(cudaFree(slot.device));
132 slot.device = nullptr;
133 }
134 slot.capacity = 0;
135 const std::size_t newCapacity = std::max<std::size_t>(n, 1024);
136 ERRCHECK(cudaMallocHost(reinterpret_cast<void **>(&slot.host), newCapacity));
137 ERRCHECK(cudaMalloc(reinterpret_cast<void **>(&slot.device), newCapacity));
138 slot.capacity = newCapacity;
139 }
140 if (slot.event == nullptr) {
142 }
143 return slot;
144 }
145
146 /// Mark the last enqueued use of the slot on the stream. The slot will not
147 /// be handed out again before that work has completed.
148 void release(Slot &slot, cudaStream_t stream)
149 {
150 ERRCHECK(cudaEventRecord(slot.event, stream));
151 slot.inFlight = true;
152 }
153
154 /// A persistent slot for a deferred device-to-host readback: an
155 /// asynchronous copy delivers device results (e.g. evaluation error
156 /// counters) into the pinned host buffer, and flushDeferred() forwards
157 /// them to the destination in the caller's memory once the stream was
158 /// synchronized. Slots stay valid from acquireDeferred() until the flush.
159 struct DeferredSlot {
160 char *host = nullptr; // pinned host memory
161 std::size_t capacity = 0;
162 double *dst = nullptr;
163 std::size_t nPending = 0;
164 };
165
166 DeferredSlot &acquireDeferred(std::size_t n)
167 {
168 if (_deferredCursor == _deferredSlots.size()) {
169 _deferredSlots.emplace_back();
170 }
171 DeferredSlot &slot = _deferredSlots[_deferredCursor++];
172 if (slot.capacity < n) {
173 // The slot is idle here: its previous use ended with the flush after
174 // a stream synchronization. Reset the state before reallocating for
175 // exception safety, like in acquire().
176 if (slot.host) {
178 slot.host = nullptr;
179 }
180 slot.capacity = 0;
181 ERRCHECK(cudaMallocHost(reinterpret_cast<void **>(&slot.host), n));
182 slot.capacity = n;
183 }
184 return slot;
185 }
186
187 /// Copy the completed readbacks to their destinations. Must only be
188 /// called after the stream was synchronized.
189 void flushDeferred()
190 {
191 for (std::size_t i = 0; i < _deferredCursor; ++i) {
192 DeferredSlot &slot = _deferredSlots[i];
193 if (slot.dst) {
194 std::memcpy(slot.dst, slot.host, slot.nPending * sizeof(double));
195 slot.dst = nullptr;
196 slot.nPending = 0;
197 }
198 }
199 _deferredCursor = 0;
200 }
201
203 {
204 // Don't use ERRCHECK here: throwing from a destructor would terminate.
205 for (Slot &slot : _slots) {
206 if (slot.inFlight)
208 if (slot.event)
209 cudaEventDestroy(slot.event);
210 if (slot.host)
211 cudaFreeHost(slot.host);
212 if (slot.device)
213 cudaFree(slot.device);
214 }
215 for (DeferredSlot &slot : _deferredSlots) {
216 if (slot.host)
217 cudaFreeHost(slot.host);
218 }
219 }
220
221private:
222 std::array<Slot, 64> _slots;
223 std::size_t _next = 0;
224 std::vector<DeferredSlot> _deferredSlots;
225 std::size_t _deferredCursor = 0;
226};
227
228} // namespace
229
230std::vector<void (*)(Batches &)> getFunctions();
231
232/// This class overrides some RooBatchComputeInterface functions, for the
233/// purpose of providing a cuda specific implementation of the library.
235
236public:
238 {
239 dispatchCUDA = this; // Set the dispatch pointer to this instance of the library upon loading
240 }
241
242 Architecture architecture() const override { return Architecture::CUDA; }
243 std::string architectureName() const override { return "cuda"; }
244
245 /** Compute multiple values using cuda kernels.
246 This method creates a Batches object and passes it to the correct compute function.
247 The compute function is launched as a cuda kernel.
248 \param computer An enum specifying the compute function to be used.
249 \param output The array where the computation results are stored.
250 \param vars A std::span containing pointers to the variables involved in the computation.
251 \param extraArgs An optional std::span containing extra double values that may participate in the computation. **/
252 void compute(RooBatchCompute::Config const &cfg, Computer computer, std::span<double> output, VarSpan vars,
253 ArgSpan extraArgs) override
254 {
255 using namespace CudaInterface;
256
257 std::size_t nEvents = output.size();
258
259 const std::size_t memSize = sizeof(Batches) + vars.size() * sizeof(Batch) + vars.size() * sizeof(double) +
260 extraArgs.size() * sizeof(double);
261
262 cudaStream_t stream = *cfg.cudaStream();
263 StreamScratch &streamScratch = scratch(cfg.cudaStream());
264 StreamScratch::Slot &slot = streamScratch.acquire(memSize);
265
266 // The staging area has the same layout in the pinned host buffer and in
267 // the device buffer, so it can be uploaded with a single copy.
268 auto batches = reinterpret_cast<Batches *>(slot.host);
269 auto arrays = reinterpret_cast<Batch *>(batches + 1);
270 auto scalarBuffer = reinterpret_cast<double *>(arrays + vars.size());
271 auto extraArgsHost = reinterpret_cast<double *>(scalarBuffer + vars.size());
272
273 auto batchesDevice = reinterpret_cast<Batches *>(slot.device);
274 auto arraysDevice = reinterpret_cast<Batch *>(batchesDevice + 1);
275 auto scalarBufferDevice = reinterpret_cast<double *>(arraysDevice + vars.size());
276 auto extraArgsDevice = reinterpret_cast<double *>(scalarBufferDevice + vars.size());
277
278 fillBatches(*batches, output.data(), nEvents, vars.size(), extraArgs.size());
279 fillArrays(arrays, vars, scalarBuffer, scalarBufferDevice, nEvents);
280 batches->args = arraysDevice;
281
282 if (!extraArgs.empty()) {
283 std::copy(std::cbegin(extraArgs), std::cend(extraArgs), extraArgsHost);
284 batches->extra = extraArgsDevice;
285 }
286
287 copyHostToDevice(slot.host, slot.device, memSize, cfg.cudaStream());
288
289 const int gridSize = getGridSize(nEvents);
290 _computeFunctions[computer]<<<gridSize, blockSize, 0, stream>>>(*batchesDevice);
291
292 // Only the NormalizedPdf computer mutates its extra args: it uses them
293 // as output parameters for the evaluation error counts. Instead of
294 // synchronizing the stream to read the counters back immediately, the
295 // readback is deferred to avoid stalling the pipeline: an asynchronous
296 // copy delivers them into a persistent pinned buffer, and the next
297 // synchronizeCudaStream() call forwards them to the caller's span. The
298 // caller's memory therefore has to stay valid until then.
299 if (computer == NormalizedPdf && !extraArgs.empty()) {
300 const std::size_t nBytes = extraArgs.size() * sizeof(double);
301 StreamScratch::DeferredSlot &deferredSlot = streamScratch.acquireDeferred(nBytes);
303 deferredSlot.dst = extraArgs.data();
304 deferredSlot.nPending = extraArgs.size();
305 }
306
307 streamScratch.release(slot, stream);
308 }
309 /// Return the sum of an input array
310 double reduceSum(RooBatchCompute::Config const &cfg, InputArr input, size_t n) override;
311 ReduceNLLOutput reduceNLL(RooBatchCompute::Config const &cfg, std::span<const double> probas,
312 std::span<const double> weights, std::span<const double> offsetProbas) override;
313
314 std::unique_ptr<AbsBufferManager> createBufferManager() const override;
315
317 void deleteCudaStream(CudaInterface::CudaStream *stream) const override
318 {
319 _scratchMap.erase(stream);
320 delete stream;
321 }
323 {
325 // Deliver deferred readbacks (e.g. the evaluation error counters from
326 // compute()) that have completed with the synchronization.
327 auto found = _scratchMap.find(stream);
328 if (found != _scratchMap.end()) {
329 found->second.flushDeferred();
330 }
331 }
332
333private:
334 StreamScratch &scratch(CudaInterface::CudaStream *stream) { return _scratchMap[stream]; }
335
336 const std::vector<void (*)(Batches &)> _computeFunctions;
337 mutable std::unordered_map<CudaInterface::CudaStream *, StreamScratch> _scratchMap;
338
339}; // End class RooBatchComputeClass
340
341inline __device__ void kahanSumUpdate(double &sum, double &carry, double a, double otherCarry)
342{
343 // c is zero the first time around. Then is done a summation as the c variable is NEGATIVE
344 const double y = a - (carry + otherCarry);
345 const double t = sum + y; // Alas, sum is big, y small, so low-order digits of y are lost.
346
347 // (t - sum) cancels the high-order part of y; subtracting y recovers NEGATIVE (low part of y)
348 carry = (t - sum) - y;
349
350 // Algebraically, c should always be zero. Beware overly-aggressive optimizing compilers!
351 sum = t;
352}
353
354// This is the same implementation of the ROOT::Math::KahanSum::operator+=(KahanSum) but in GPU
355inline __device__ void kahanSumReduction(double *shared, size_t n, double *__restrict__ result, int carry_index)
356{
357 // Stride in first iteration = half of the block dim. Then the half of the half...
358 for (int i = blockDim.x / 2; i > 0; i >>= 1) {
359 if (threadIdx.x < i && (threadIdx.x + i) < n) {
360 kahanSumUpdate(shared[threadIdx.x], shared[carry_index], shared[threadIdx.x + i], shared[carry_index + i]);
361 }
363 } // Next time around, the lost low part will be added to y in a fresh attempt.
364 // Wait until all threads of the block have finished its work
365
366 if (threadIdx.x == 0) {
367 result[blockIdx.x] = shared[0];
368 result[blockIdx.x + gridDim.x] = shared[carry_index];
369 }
370}
371
372__global__ void kahanSum(const double *__restrict__ input, const double *__restrict__ carries, size_t n,
373 double *__restrict__ result, bool nll)
374{
375 int thIdx = threadIdx.x;
376 int gthIdx = thIdx + blockIdx.x * blockSize;
377 int carry_index = threadIdx.x + blockDim.x;
378 const int nThreadsTotal = blockSize * gridDim.x;
379
380 // The first half of the shared memory is for storing the summation and the second half for the carry or compensation
381 extern __shared__ double shared[];
382
383 double sum = 0.0;
384 double carry = 0.0;
385
386 for (int i = gthIdx; i < n; i += nThreadsTotal) {
387 // Note: it does not make sense to use the nll option and provide at the
388 // same time external carries.
389 double val = nll == 1 ? -std::log(input[i]) : input[i];
390 kahanSumUpdate(sum, carry, val, carries ? carries[i] : 0.0);
391 }
392
393 shared[thIdx] = sum;
394 shared[carry_index] = carry;
395
396 // Wait until all threads in each block have loaded their elements
398
400}
401
402/// Computes the negative log likelihood sum with the same semantics as the
403/// CPU implementation of RooBatchComputeInterface::reduceNLL(): zero-weight
404/// events are skipped, and evaluation problems are counted and accumulated
405/// into a "badness" value that the host can pack into a NaN for the error
406/// recovery in the minimizer. The `stats` output has the layout
407/// [badness, nNonPositive, nNaN, nInfinite] and must be zero-initialized.
408__global__ void nllSumKernel(const double *__restrict__ probas, const double *__restrict__ weights,
409 const double *__restrict__ offsetProbas, size_t nProbas, double scalarProba,
410 size_t nWeights, double *__restrict__ result, double *__restrict__ stats)
411{
412 int thIdx = threadIdx.x;
413 int gthIdx = thIdx + blockIdx.x * blockSize;
414 int carry_index = threadIdx.x + blockDim.x;
415 const int nThreadsTotal = blockSize * gridDim.x;
416
417 // The first half of the shared memory is for storing the summation and the second half for the carry or compensation
418 extern __shared__ double shared[];
419
420 double sum = 0.0;
421 double carry = 0.0;
422 double badness = 0.0;
423 unsigned int nNonPositive = 0;
424 unsigned int nNaN = 0;
425 unsigned int nInfinite = 0;
426
427 for (int i = gthIdx; i < nWeights; i += nThreadsTotal) {
428 const double weight = weights[i];
429 // Zero-weight events don't contribute to the likelihood. Skipping them
430 // also avoids 0 * inf = NaN for zero probabilities.
431 if (weight == 0.0) {
432 continue;
433 }
434 const double proba = nProbas == 1 ? scalarProba : probas[i];
435 double term;
436 if (proba <= 0.0) {
437 ++nNonPositive;
438 badness += -proba;
439 term = std::log(proba);
440 } else if (std::isnan(proba)) {
441 ++nNaN;
443 term = proba;
444 } else {
445 if (std::isinf(proba)) {
446 ++nInfinite;
447 }
448 term = std::log(proba);
449 }
450 if (offsetProbas)
451 term -= std::log(offsetProbas[i]);
452 term *= -weight;
453 kahanSumUpdate(sum, carry, term, 0.0);
454 }
455
456 // Accumulate the evaluation error statistics over the whole grid. These
457 // atomics are on the rare path: they are only executed by threads that
458 // actually encountered problematic values.
459 if (badness != 0.0)
460 atomicAdd(&stats[0], badness);
461 if (nNonPositive != 0)
462 atomicAdd(&stats[1], double(nNonPositive));
463 if (nNaN != 0)
464 atomicAdd(&stats[2], double(nNaN));
465 if (nInfinite != 0)
466 atomicAdd(&stats[3], double(nInfinite));
467
468 shared[thIdx] = sum;
469 shared[carry_index] = carry;
470
471 // Wait until all threads in each block have loaded their elements
473
475}
476
478{
479 if (n == 0)
480 return 0.0;
481 const int gridSize = getGridSize(n);
482 cudaStream_t stream = *cfg.cudaStream();
483 StreamScratch &streamScratch = scratch(cfg.cudaStream());
484 StreamScratch::Slot &slot = streamScratch.acquire(2 * gridSize * sizeof(double));
485 auto devOut = reinterpret_cast<double *>(slot.device);
486 auto hostOut = reinterpret_cast<double *>(slot.host);
487 constexpr int shMemSize = 2 * blockSize * sizeof(double);
491 // Release right after the last enqueued use of the slot, so that the slot
492 // is protected by its event even if the synchronization below throws.
493 streamScratch.release(slot, stream);
495 return hostOut[0];
496}
497
499 std::span<const double> weights, std::span<const double> offsetProbas)
500{
501 ReduceNLLOutput out;
502 if (probas.empty()) {
503 return out;
504 }
505 const int gridSize = getGridSize(weights.size());
506 cudaStream_t stream = *cfg.cudaStream();
507 // Layout of the scratch buffer: [sum, carry, badness, nNonPositive, nNaN,
508 // nInfinite, partial sums (gridSize), partial carries (gridSize)].
509 StreamScratch &streamScratch = scratch(cfg.cudaStream());
510 StreamScratch::Slot &slot = streamScratch.acquire((6 + 2 * gridSize) * sizeof(double));
511 auto devOut = reinterpret_cast<double *>(slot.device);
512 auto hostOut = reinterpret_cast<double *>(slot.host);
513 constexpr int shMemSize = 2 * blockSize * sizeof(double);
514
515#ifndef NDEBUG
516 for (auto span : {probas, weights, offsetProbas}) {
517 // Scalar spans can point to host memory (e.g. the scalar buffer of an
518 // observable-independent pdf), so only spans with more than one element
519 // are required to be on the device.
521 assert(span.size() <= 1 || span.data() == nullptr ||
522 (cudaPointerGetAttributes(&attr, span.data()) == cudaSuccess && attr.type == cudaMemoryTypeDevice));
523 }
524#endif
525
526 // Zero-initialize the evaluation error statistics for the atomic updates.
527 ERRCHECK(cudaMemsetAsync(devOut + 2, 0, 4 * sizeof(double), stream));
528
530 probas.data(), weights.data(), offsetProbas.empty() ? nullptr : offsetProbas.data(), probas.size(),
531 probas.size() == 1 ? probas[0] : 0.0, weights.size(), devOut + 6, devOut + 2);
532
534
535 // The sum, its Kahan carry, and the evaluation error statistics are
536 // adjacent in the output buffer, so they can be read back in a single copy.
538 // Release right after the last enqueued use of the slot, so that the slot
539 // is protected by its event even if the synchronization below throws.
540 streamScratch.release(slot, stream);
542
543 out.nllSum = hostOut[0];
544 out.nllSumCarry = hostOut[1];
546 out.nNaNValues = hostOut[4];
547 out.nInfiniteValues = hostOut[5];
548
549 if (hostOut[2] != 0.0) {
550 // Some events had evaluation errors: return the accumulated "badness"
551 // of the errors packed into a NaN, like the CPU implementation, so the
552 // minimizer can use it to recover.
554 out.nllSumCarry = 0.0;
555 }
556
557 return out;
558}
559
560namespace {
561
562class ScalarBufferContainer {
563public:
564 ScalarBufferContainer() {}
565 ScalarBufferContainer(std::size_t size)
566 {
567 if (size != 1)
568 throw std::runtime_error("ScalarBufferContainer can only be of size 1");
569 }
570
571 double const *hostReadPtr() const { return &_val; }
572 double const *deviceReadPtr() const { return &_val; }
573
574 double *hostWritePtr() { return &_val; }
575 double *deviceWritePtr() { return &_val; }
576
577 void assignFromHost(std::span<const double> input) { _val = input[0]; }
578 void assignFromDevice(std::span<const double> input)
579 {
580 CudaInterface::copyDeviceToHost(input.data(), &_val, input.size(), nullptr);
581 }
582
583private:
584 double _val;
585};
586
587class CPUBufferContainer {
588public:
589 CPUBufferContainer(std::size_t size) : _vec(size) {}
590
591 double const *hostReadPtr() const { return _vec.data(); }
592 double const *deviceReadPtr() const
593 {
594 throw std::bad_function_call();
595 return nullptr;
596 }
597
598 double *hostWritePtr() { return _vec.data(); }
599 double *deviceWritePtr()
600 {
601 throw std::bad_function_call();
602 return nullptr;
603 }
604
605 void assignFromHost(std::span<const double> input) { _vec.assign(input.begin(), input.end()); }
606 void assignFromDevice(std::span<const double> input)
607 {
608 CudaInterface::copyDeviceToHost(input.data(), _vec.data(), input.size(), nullptr);
609 }
610
611private:
612 std::vector<double> _vec;
613};
614
615class GPUBufferContainer {
616public:
617 GPUBufferContainer(std::size_t size) : _arr(size) {}
618
619 double const *hostReadPtr() const
620 {
621 throw std::bad_function_call();
622 return nullptr;
623 }
624 double const *deviceReadPtr() const { return _arr.data(); }
625
626 double *hostWritePtr() const
627 {
628 throw std::bad_function_call();
629 return nullptr;
630 }
631 double *deviceWritePtr() const { return const_cast<double *>(_arr.data()); }
632
633 void assignFromHost(std::span<const double> input)
634 {
635 CudaInterface::copyHostToDevice(input.data(), deviceWritePtr(), input.size(), nullptr);
636 }
637 void assignFromDevice(std::span<const double> input)
638 {
639 CudaInterface::copyDeviceToDevice(input.data(), deviceWritePtr(), input.size(), nullptr);
640 }
641
642private:
643 CudaInterface::DeviceArray<double> _arr;
644};
645
646class PinnedBufferContainer {
647public:
648 PinnedBufferContainer(std::size_t size) : _arr{size}, _gpuBuffer{size} {}
649 std::size_t size() const { return _arr.size(); }
650
651 void setCudaStream(CudaInterface::CudaStream *stream) { _cudaStream = stream; }
652
653 double const *hostReadPtr() const
654 {
655
656 if (_lastAccess == LastAccessType::GPU_WRITE) {
657 CudaInterface::copyDeviceToHost(_gpuBuffer.deviceReadPtr(), const_cast<double *>(_arr.data()), size(),
658 _cudaStream);
659 // The copy is asynchronous, and the caller reads the host memory
660 // right away, so the stream needs to be synchronized here.
661 if (_cudaStream) {
662 ERRCHECK(cudaStreamSynchronize(*_cudaStream));
663 }
664 }
665
666 _lastAccess = LastAccessType::CPU_READ;
667 return const_cast<double *>(_arr.data());
668 }
669 double const *deviceReadPtr() const
670 {
671
672 if (_lastAccess == LastAccessType::CPU_WRITE) {
673 CudaInterface::copyHostToDevice(_arr.data(), _gpuBuffer.deviceWritePtr(), size(), _cudaStream);
674 }
675
676 _lastAccess = LastAccessType::GPU_READ;
677 return _gpuBuffer.deviceReadPtr();
678 }
679
680 double *hostWritePtr()
681 {
682 _lastAccess = LastAccessType::CPU_WRITE;
683 return _arr.data();
684 }
685 double *deviceWritePtr()
686 {
687 _lastAccess = LastAccessType::GPU_WRITE;
688 return _gpuBuffer.deviceWritePtr();
689 }
690
691 void assignFromHost(std::span<const double> input) { std::copy(input.begin(), input.end(), hostWritePtr()); }
692 void assignFromDevice(std::span<const double> input)
693 {
694 CudaInterface::copyDeviceToDevice(input.data(), deviceWritePtr(), input.size(), _cudaStream);
695 }
696
697private:
698 enum class LastAccessType {
699 CPU_READ,
700 GPU_READ,
701 CPU_WRITE,
703 };
704
705 CudaInterface::PinnedHostArray<double> _arr;
706 GPUBufferContainer _gpuBuffer;
707 CudaInterface::CudaStream *_cudaStream = nullptr;
708 mutable LastAccessType _lastAccess = LastAccessType::CPU_READ;
709};
710
711template <class Container>
712class BufferImpl : public AbsBuffer {
713public:
714 using Queue = std::queue<std::unique_ptr<Container>>;
715
716 BufferImpl(std::size_t size, Queue &queue) : _queue{queue}
717 {
718 if (_queue.empty()) {
719 _vec = std::make_unique<Container>(size);
720 } else {
721 _vec = std::move(_queue.front());
722 _queue.pop();
723 }
724 }
725
726 ~BufferImpl() override { _queue.emplace(std::move(_vec)); }
727
728 double const *hostReadPtr() const override { return _vec->hostReadPtr(); }
729 double const *deviceReadPtr() const override { return _vec->deviceReadPtr(); }
730
731 double *hostWritePtr() override { return _vec->hostWritePtr(); }
732 double *deviceWritePtr() override { return _vec->deviceWritePtr(); }
733
734 void assignFromHost(std::span<const double> input) override { _vec->assignFromHost(input); }
735 void assignFromDevice(std::span<const double> input) override { _vec->assignFromDevice(input); }
736
737 Container &vec() { return *_vec; }
738
739private:
740 std::unique_ptr<Container> _vec;
741 Queue &_queue;
742};
743
748
749struct BufferQueuesMaps {
750 std::map<std::size_t, ScalarBuffer::Queue> scalarBufferQueuesMap;
751 std::map<std::size_t, CPUBuffer::Queue> cpuBufferQueuesMap;
752 std::map<std::size_t, GPUBuffer::Queue> gpuBufferQueuesMap;
753 std::map<std::size_t, PinnedBuffer::Queue> pinnedBufferQueuesMap;
754};
755
756class BufferManager : public AbsBufferManager {
757
758public:
759 BufferManager() : _queuesMaps{std::make_unique<BufferQueuesMaps>()} {}
760
761 std::unique_ptr<AbsBuffer> makeScalarBuffer() override
762 {
763 return std::make_unique<ScalarBuffer>(1, _queuesMaps->scalarBufferQueuesMap[1]);
764 }
765 std::unique_ptr<AbsBuffer> makeCpuBuffer(std::size_t size) override
766 {
767 return std::make_unique<CPUBuffer>(size, _queuesMaps->cpuBufferQueuesMap[size]);
768 }
769 std::unique_ptr<AbsBuffer> makeGpuBuffer(std::size_t size) override
770 {
771 return std::make_unique<GPUBuffer>(size, _queuesMaps->gpuBufferQueuesMap[size]);
772 }
773 std::unique_ptr<AbsBuffer> makePinnedBuffer(std::size_t size, CudaInterface::CudaStream *stream = nullptr) override
774 {
775 auto out = std::make_unique<PinnedBuffer>(size, _queuesMaps->pinnedBufferQueuesMap[size]);
776 out->vec().setCudaStream(stream);
777 return out;
778 }
779
780private:
781 std::unique_ptr<BufferQueuesMaps> _queuesMaps;
782};
783
784} // namespace
785
786std::unique_ptr<AbsBufferManager> RooBatchComputeClass::createBufferManager() const
787{
788 return std::make_unique<BufferManager>();
789}
790
791/// Static object to trigger the constructor which overwrites the dispatch pointer.
793
794} // End namespace CUDA
795} // End namespace RooBatchCompute
#define a(i)
Definition RSha256.hxx:99
std::vector< double > _vec
std::array< Slot, 64 > _slots
char * host
double _val
CudaInterface::CudaStream * _cudaStream
std::map< std::size_t, CPUBuffer::Queue > cpuBufferQueuesMap
std::map< std::size_t, ScalarBuffer::Queue > scalarBufferQueuesMap
double * dst
std::vector< DeferredSlot > _deferredSlots
Queue & _queue
bool inFlight
std::size_t nPending
CudaInterface::DeviceArray< double > _arr
std::size_t capacity
std::map< std::size_t, PinnedBuffer::Queue > pinnedBufferQueuesMap
LastAccessType _lastAccess
GPUBufferContainer _gpuBuffer
std::unique_ptr< BufferQueuesMaps > _queuesMaps
std::size_t _deferredCursor
std::map< std::size_t, GPUBuffer::Queue > gpuBufferQueuesMap
std::size_t _next
char * device
double badness
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.
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
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t attr
These classes encapsulate the necessary data for the computations.
This class overrides some RooBatchComputeInterface functions, for the purpose of providing a cuda spe...
ReduceNLLOutput reduceNLL(RooBatchCompute::Config const &cfg, std::span< const double > probas, std::span< const double > weights, std::span< const double > offsetProbas) override
std::unordered_map< CudaInterface::CudaStream *, StreamScratch > _scratchMap
const std::vector< void(*)(Batches &)> _computeFunctions
CudaInterface::CudaStream * newCudaStream() const override
std::unique_ptr< AbsBufferManager > createBufferManager() const override
void deleteCudaStream(CudaInterface::CudaStream *stream) const override
void synchronizeCudaStream(CudaInterface::CudaStream *stream) const override
Wait until all work that was enqueued on the stream has completed.
double reduceSum(RooBatchCompute::Config const &cfg, InputArr input, size_t n) override
Return the sum of an input array.
StreamScratch & scratch(CudaInterface::CudaStream *stream)
std::string architectureName() const override
Architecture architecture() const override
void compute(RooBatchCompute::Config const &cfg, Computer computer, std::span< double > output, VarSpan vars, ArgSpan extraArgs) override
Compute multiple values using cuda kernels.
Minimal configuration struct to steer the evaluation of a single node with the RooBatchCompute librar...
CudaInterface::CudaStream * cudaStream() const
The interface which should be implemented to provide optimised computation functions for implementati...
Double_t y[n]
Definition legend1.C:17
const Int_t n
Definition legend1.C:16
std::vector< void(*)(Batches &)> getFunctions()
Returns a std::vector of pointers to the compute functions in this file.
static RooBatchComputeClass computeObj
Static object to trigger the constructor which overwrites the dispatch pointer.
__global__ void kahanSum(const double *__restrict__ input, const double *__restrict__ carries, size_t n, double *__restrict__ result, bool nll)
__global__ void nllSumKernel(const double *__restrict__ probas, const double *__restrict__ weights, const double *__restrict__ offsetProbas, size_t nProbas, double scalarProba, size_t nWeights, double *__restrict__ result, double *__restrict__ stats)
Computes the negative log likelihood sum with the same semantics as the CPU implementation of RooBatc...
__device__ void kahanSumReduction(double *shared, size_t n, double *__restrict__ result, int carry_index)
__device__ void kahanSumUpdate(double &sum, double &carry, double a, double otherCarry)
void copyDeviceToDevice(const T *src, T *dest, std::size_t n, CudaStream *stream=nullptr)
Copies data from the CUDA device to the CUDA device.
void copyHostToDevice(const T *src, T *dest, std::size_t n, CudaStream *stream=nullptr)
Copies data from the host to the CUDA device.
void copyDeviceToHost(const T *src, T *dest, std::size_t n, CudaStream *stream=nullptr)
Copies data from the CUDA device to the host.
Namespace for dispatching RooFit computations to various backends.
R__EXTERN RooBatchComputeInterface * dispatchCUDA
std::span< double > ArgSpan
const double *__restrict InputArr
std::span< const std::span< const double > > VarSpan
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...
static uint64_t sum(uint64_t i)
Definition Factory.cxx:2335