Project
Loading...
Searching...
No Matches
GPUTPCNNClusterizerKernels.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
14
15#include "clusterFinderDefs.h"
16#include "PackedCharge.h"
18#include "GPUConstantMem.h"
19#include "GPUTPCClusterFinder.h"
20#include "GPUTPCGeometry.h"
21
22using namespace o2::gpu;
23using namespace o2::gpu::tpccf;
24
25#include "CfConsts.h"
26#include "CfUtils.h"
27#include "ClusterAccumulator.h"
29
30#if !defined(GPUCA_GPUCODE)
31#include "GPUHostDataTypes.h"
32#include "MCLabelAccumulator.h"
33#endif
34
35#ifdef GPUCA_GPUCODE
36#include "GPUTPCCFClusterizer.inc"
37#endif
38
40
41// Defining individual thread functions for data filling, determining the class label and running the CF clusterizer
42template <>
43GPUdii() void GPUTPCNNClusterizerKernels::Thread<GPUTPCNNClusterizerKernels::runCfClusterizer>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& processors, uint8_t sector, int8_t dtype, int8_t withMC, uint32_t batchStart)
44{
45 uint32_t glo_idx = get_global_id(0);
46 auto& clusterer = processors.tpcClusterer[sector];
47 auto& clustererNN = processors.tpcNNClusterer[sector];
48 CfArray2D<PackedCharge> chargeMap(reinterpret_cast<PackedCharge*>(clusterer.mPchargeMap));
49 CPU_ONLY(MCLabelAccumulator labelAcc(clusterer));
50 tpc::ClusterNative* clusterOut = clusterer.mPclusterByRow;
51 int8_t isAccepted = (clustererNN.mNnClusterizerUseClassification ? (clustererNN.mOutputDataClass[CAMath::Min(glo_idx, (uint32_t)clusterer.mPmemory->counters.nClusters - 1)] > 0) : 1);
52 GPUTPCCFClusterizer::computeClustersImpl(get_num_groups(0), get_local_size(0), get_group_id(0), get_local_id(0), clusterer, clusterer.mPmemory->fragment, smem, chargeMap, clusterer.mPfilteredPeakPositions, clusterer.Param().rec, CPU_PTR(&labelAcc), clusterer.mPmemory->counters.nClusters, clusterer.mNMaxClusterPerRow, clusterer.mPclusterInRow, clusterOut, clusterer.mPclusterPosInRow, isAccepted);
53}
54
55template <>
56GPUdii() void GPUTPCNNClusterizerKernels::Thread<GPUTPCNNClusterizerKernels::fillInputNNCPU>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& processors, uint8_t sector, int8_t dtype, int8_t withMC, uint32_t batchStart)
57{
58 auto& clusterer = processors.tpcClusterer[sector];
59 auto& clustererNN = processors.tpcNNClusterer[sector];
60
61 const uint32_t glo_idx = get_global_id(0);
62 if (glo_idx + batchStart >= clusterer.mPmemory->counters.nClusters || glo_idx >= (uint32_t)clustererNN.mNnClusterizerBatchedMode) {
63 return;
64 }
65
66 uint32_t write_idx = glo_idx * clustererNN.mNnClusterizerElementSize;
67
68 CfArray2D<PackedCharge> chargeMap(reinterpret_cast<PackedCharge*>(clusterer.mPchargeMap));
69 CfArray2D<uint8_t> isPeakMap(clusterer.mPpeakMap);
70 CfChargePos peak = clusterer.mPfilteredPeakPositions[CAMath::Min(glo_idx + batchStart, (uint32_t)(clusterer.mPmemory->counters.nClusters - 1))];
71 const int32_t row = static_cast<int>(peak.row());
72 const int32_t pad = static_cast<int>(peak.pad());
73 const int32_t time = static_cast<int>(peak.time());
74 const float central_charge = static_cast<float>(chargeMap[peak].unpack());
75 const float inverse_charge = 1.f / central_charge;
76
77 const int32_t row_offset = GPUTPCNNClusterizerKernels::rowOffset(row, clustererNN.mNnClusterizerSizeInputRow);
78 const int32_t iroc_row = 63 + clustererNN.mNnClusterizerSizeInputRow;
79 const int32_t maxrow = o2::tpc::constants::MAXGLOBALPADROW + clustererNN.mNnClusterizerSizeInputRow;
80 const int32_t npads_row = GPUTPCGeometry::NPads(row);
81 float output_value = clustererNN.mNnClusterizerBoundaryFillValue;
82
83 for (int32_t target_row = -clustererNN.mNnClusterizerSizeInputRow + row; target_row <= clustererNN.mNnClusterizerSizeInputRow + row; ++target_row) {
84 uint8_t is_boundary = (target_row < 0) || (target_row >= o2::tpc::constants::MAXGLOBALPADROW);
85 const int32_t p_local = pad + (is_boundary ? 0 : GPUTPCNNClusterizerKernels::padOffset(row, target_row));
86 const int32_t npads_reference = is_boundary ? 0 : GPUTPCGeometry::NPads(target_row - row_offset);
87
88 for (int32_t target_pad = -clustererNN.mNnClusterizerSizeInputPad + p_local; target_pad <= clustererNN.mNnClusterizerSizeInputPad + p_local; ++target_pad) {
89 is_boundary = is_boundary || GPUTPCNNClusterizerKernels::isBoundary(target_row + row_offset, target_pad, maxrow, iroc_row, npads_row, npads_reference);
90
91 for (int32_t target_time = -clustererNN.mNnClusterizerSizeInputTime + time; target_time <= clustererNN.mNnClusterizerSizeInputTime + time; ++target_time) {
92 if (is_boundary || target_time < 0 || target_time >= clustererNN.maxAllowedTimebin) {
93 // Fill boundary value
94 output_value = clustererNN.mNnClusterizerBoundaryFillValue;
95 if (dtype == 0) {
96 clustererNN.mInputData_32[write_idx] = output_value;
97 } else {
98 clustererNN.mInputData_16[write_idx] = (OrtDataType::Float16_t)output_value;
99 }
100 } else {
101 CfChargePos tmp_pos(target_row, target_pad, target_time);
102 output_value = chargeMap[tmp_pos].unpack() * inverse_charge;
103 if (dtype == 0) {
104 clustererNN.mInputData_32[write_idx] = output_value;
105 } else {
106 clustererNN.mInputData_16[write_idx] = (OrtDataType::Float16_t)output_value;
107 }
108 }
109 // if((CAMath::Abs(static_cast<float>(clustererNN.mInputData_16_Test[write_idx]) - static_cast<float>(clustererNN.mInputData_16[write_idx])) > 1e-4) && ((glo_idx + batchStart) < clusterer.mPmemory->counters.nClusters)) {
110 // printf("Warning: Input data mismatch at index %d, %d - row, pad, time: %d, %d, %d : %f -> %f\n", glo_idx, glo_idx + batchStart, r, p, t,
111 // static_cast<float>(clustererNN.mInputData_16_Test[write_idx]), static_cast<float>(clustererNN.mInputData_16[write_idx]));
112 // }
113 write_idx++;
114 }
115 }
116 }
117
118 if (clustererNN.mNnClusterizerAddIndexData) {
119 if (dtype == 0) {
120 clustererNN.mInputData_32[write_idx] = static_cast<float>(sector) / o2::tpc::constants::MAXSECTOR;
121 clustererNN.mInputData_32[write_idx + 1] = static_cast<float>(row) / o2::tpc::constants::MAXGLOBALPADROW;
122 clustererNN.mInputData_32[write_idx + 2] = static_cast<float>(pad) / npads_row;
123 } else {
124 clustererNN.mInputData_16[write_idx] = (OrtDataType::Float16_t)(static_cast<float>(sector) / o2::tpc::constants::MAXSECTOR);
125 clustererNN.mInputData_16[write_idx + 1] = (OrtDataType::Float16_t)(static_cast<float>(row) / o2::tpc::constants::MAXGLOBALPADROW);
126 clustererNN.mInputData_16[write_idx + 2] = (OrtDataType::Float16_t)(static_cast<float>(pad) / npads_row);
127 }
128 }
129
130 if (!clustererNN.mNnClusterizerSetDeconvolutionFlags) {
131 clustererNN.mClusterFlags[2 * glo_idx] = 0;
132 clustererNN.mClusterFlags[2 * glo_idx + 1] = 0;
133
134 for (uint16_t i = 0; i < 8; ++i) {
135 Delta2 d = cfconsts::InnerNeighbors[i];
136 CfChargePos tmp_pos = peak.delta(d);
137 clustererNN.mClusterFlags[2 * glo_idx] += CfUtils::isPeak(isPeakMap[tmp_pos]);
138 }
139 clustererNN.mClusterFlags[2 * glo_idx + 1] = clustererNN.mClusterFlags[2 * glo_idx];
140 }
141}
142
143template <>
144GPUdii() void GPUTPCNNClusterizerKernels::Thread<GPUTPCNNClusterizerKernels::fillInputNNGPU>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& processors, uint8_t sector, int8_t dtype, int8_t withMC, uint32_t batchStart)
145{
146 // Statically quantized S8S8 ONNX models with graph-contained scaling expose
147 // FP32 graph boundaries and must use dtype == 0 here. Their QuantizeLinear
148 // nodes execute on the ONNX GPU stream. dtype == 2 is reserved for models
149 // with true external INT8 I/O and requires matching external scale metadata.
150 const uint32_t glo_idx = get_global_id(0);
151 auto& clusterer = processors.tpcClusterer[sector];
152 auto& clustererNN = processors.tpcNNClusterer[sector];
153
154 if (glo_idx >= (uint32_t)clustererNN.mNnClusterizerBatchedMode * clustererNN.mNnClusterizerRowTimeSizeThreads) {
155 return;
156 }
157
158 const uint32_t base_idx = glo_idx / clustererNN.mNnClusterizerRowTimeSizeThreads;
159 const uint32_t transient_index = glo_idx - (base_idx * clustererNN.mNnClusterizerRowTimeSizeThreads);
160
161 // Early exit for out-of-bounds threads
162 if (base_idx + batchStart >= clusterer.mPmemory->counters.nClusters) {
163 return;
164 }
165 CfArray2D<PackedCharge> chargeMap(reinterpret_cast<PackedCharge*>(clusterer.mPchargeMap));
166 CfArray2D<uint8_t> isPeakMap(clusterer.mPpeakMap);
167
168 // Use dedicated neural network shared memory arrays for warp-level caching
169 // First thread in each warp loads shared data
170 CfChargePos peak = clusterer.mPfilteredPeakPositions[CAMath::Min(base_idx + batchStart, (uint32_t)(clusterer.mPmemory->counters.nClusters - 1))];
171 const float central_charge = chargeMap[peak].unpack();
172 const int32_t row = static_cast<int>(peak.row());
173 const int32_t pad = static_cast<int>(peak.pad());
174 const int32_t time = static_cast<int>(peak.time());
175
176 // Handle index data with fewer branches
177 if (clustererNN.mNnClusterizerAddIndexData && transient_index >= clustererNN.mNnClusterizerRowTimeSize) {
178 uint32_t write_idx = base_idx * clustererNN.mNnClusterizerElementSize + clustererNN.mNnClusterizerChargeArraySize;
179 const int32_t npads = GPUTPCGeometry::NPads(row);
180 if (dtype == 0) {
181 clustererNN.mInputData_32[write_idx] = static_cast<float>(sector) / o2::tpc::constants::MAXSECTOR;
182 clustererNN.mInputData_32[write_idx + 1] = static_cast<float>(row) / o2::tpc::constants::MAXGLOBALPADROW;
183 clustererNN.mInputData_32[write_idx + 2] = static_cast<float>(pad) / npads;
184 } else {
185 clustererNN.mInputData_16[write_idx] = (OrtDataType::Float16_t)(static_cast<float>(sector) / o2::tpc::constants::MAXSECTOR);
186 clustererNN.mInputData_16[write_idx + 1] = (OrtDataType::Float16_t)(static_cast<float>(row) / o2::tpc::constants::MAXGLOBALPADROW);
187 clustererNN.mInputData_16[write_idx + 2] = (OrtDataType::Float16_t)(static_cast<float>(pad) / npads);
188 }
189 }
190
191 // Main data processing - optimize index calculations
192 if (transient_index < clustererNN.mNnClusterizerRowTimeSize) {
193 // Optimize 3D index calculation
194 const int32_t row_idx = transient_index / clustererNN.mNnClusterizerFullTimeSize;
195 const int32_t time_idx = transient_index - row_idx * clustererNN.mNnClusterizerFullTimeSize;
196 int32_t write_idx = base_idx * clustererNN.mNnClusterizerElementSize + row_idx * clustererNN.mNnClusterizerPadTimeSize + time_idx;
197
198 // Early boundary check for row
199 const int32_t target_row = row + row_idx - clustererNN.mNnClusterizerSizeInputRow;
200 float output_value = clustererNN.mNnClusterizerBoundaryFillValue;
201
202 if ((row < 63 && target_row > 62) || (target_row < 0) || (row > 62 && target_row < 63) || (target_row >= o2::tpc::constants::MAXGLOBALPADROW)) {
203 for (uint32_t target_pad = 0; target_pad < clustererNN.mNnClusterizerFullPadSize; ++target_pad) {
204 if (dtype == 0) {
205 clustererNN.mInputData_32[write_idx] = output_value;
206 } else {
207 clustererNN.mInputData_16[write_idx] = (OrtDataType::Float16_t)output_value;
208 }
209 write_idx += clustererNN.mNnClusterizerFullTimeSize;
210 }
211 return;
212 } else {
213 // Calculate offsets
214 const int32_t target_time = time + time_idx - clustererNN.mNnClusterizerSizeInputTime;
215 const uint8_t is_time_boundary = (target_time < 0) || (target_time >= clustererNN.maxAllowedTimebin);
216 const float inverse_central_charge = 1.f / central_charge; // multiply by inverse is cheaper than divide
217 const int32_t p_local = pad + GPUTPCNNClusterizerKernels::padOffset(row, target_row);
218 const int32_t npads = GPUTPCGeometry::NPads(target_row);
219
220 const int32_t start_pad = -clustererNN.mNnClusterizerSizeInputPad + p_local;
221 const int32_t end_pad = clustererNN.mNnClusterizerSizeInputPad + p_local;
222
223 for (int32_t target_pad = start_pad; target_pad <= end_pad; ++target_pad) {
224 if (target_pad >= npads || target_pad < 0 || is_time_boundary) {
225 output_value = clustererNN.mNnClusterizerBoundaryFillValue;
226 } else {
227 CfChargePos pos(target_row, target_pad, target_time);
228 // one load + one multiply
229 output_value = chargeMap[pos].unpack() * inverse_central_charge;
230 }
231 if (dtype == 0) {
232 clustererNN.mInputData_32[write_idx] = output_value;
233 } else {
234 clustererNN.mInputData_16[write_idx] = (OrtDataType::Float16_t)output_value;
235 }
236 write_idx += clustererNN.mNnClusterizerFullTimeSize;
237 }
238 return;
239 }
240 }
241}
242
243template <>
244GPUdii() void GPUTPCNNClusterizerKernels::Thread<GPUTPCNNClusterizerKernels::determineClass1Labels>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& processors, uint8_t sector, int8_t dtype, int8_t withMC, uint32_t batchStart)
245{
246 uint32_t glo_idx = get_global_id(0);
247 auto& clusterer = processors.tpcClusterer[sector];
248 auto& clustererNN = processors.tpcNNClusterer[sector];
249 if (glo_idx + batchStart >= clusterer.mPmemory->counters.nClusters || glo_idx >= (uint32_t)clustererNN.mNnClusterizerBatchedMode) {
250 return;
251 }
252 if (clustererNN.mNnClusterizerUseClassification) {
253 if (dtype == 0) {
254 clustererNN.mOutputDataClass[glo_idx + batchStart] = (int32_t)(clustererNN.mModelProbabilities_32[glo_idx] > clustererNN.mNnClassThreshold);
255 } else {
256 clustererNN.mOutputDataClass[glo_idx + batchStart] = (int32_t)((clustererNN.mModelProbabilities_16[glo_idx]).ToFloat() > clustererNN.mNnClassThreshold);
257 }
258 } else {
259 clustererNN.mOutputDataClass[glo_idx + batchStart] = 1;
260 }
261}
262
263template <>
264GPUdii() void GPUTPCNNClusterizerKernels::Thread<GPUTPCNNClusterizerKernels::determineClass2Labels>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& processors, uint8_t sector, int8_t dtype, int8_t withMC, uint32_t batchStart)
265{
266 uint32_t glo_idx = get_global_id(0);
267 auto& clusterer = processors.tpcClusterer[sector];
268 auto& clustererNN = processors.tpcNNClusterer[sector];
269 if (glo_idx + batchStart >= clusterer.mPmemory->counters.nClusters || glo_idx >= (uint32_t)clustererNN.mNnClusterizerBatchedMode) {
270 return;
271 }
272 if (clustererNN.mNnClusterizerUseClassification) {
273 uint32_t elem_iterator = glo_idx * clustererNN.mNnClusterizerModelClassNumOutputNodes;
274 float current_max_prob = 0.f; // If the neural network doesn't contain the softmax as a last layer, the outputs can range in [-infty, infty]
275 uint32_t class_label = 0;
276 for (uint32_t pIdx = elem_iterator; pIdx < elem_iterator + clustererNN.mNnClusterizerModelClassNumOutputNodes; pIdx++) {
277 if (pIdx == elem_iterator) {
278 if (dtype == 0) {
279 current_max_prob = clustererNN.mModelProbabilities_32[pIdx];
280 } else {
281 current_max_prob = static_cast<float>(clustererNN.mModelProbabilities_16[pIdx]);
282 }
283 } else {
284 if (dtype == 0) {
285 current_max_prob = CAMath::Max(current_max_prob, clustererNN.mModelProbabilities_32[pIdx]);
286 } else {
287 current_max_prob = CAMath::Max(current_max_prob, clustererNN.mModelProbabilities_16[pIdx].ToFloat());
288 }
289 }
290 }
291 // uint32_t class_label = std::distance(elem_iterator, std::max_element(elem_iterator, elem_iterator + clustererNN.mNnClusterizerModelClassNumOutputNodes)); // Multiple outputs of the class network are the probabilities for each class. The highest one "wins"
292 clustererNN.mOutputDataClass[glo_idx + batchStart] = class_label;
293 if (class_label > 1) {
294 clustererNN.mClusterFlags[2 * glo_idx] = 1;
295 clustererNN.mClusterFlags[2 * glo_idx + 1] = 1;
296 }
297 } else {
298 clustererNN.mOutputDataClass[glo_idx + batchStart] = 1;
299 }
300}
301
302template <>
303GPUdii() void GPUTPCNNClusterizerKernels::Thread<GPUTPCNNClusterizerKernels::publishClass1Regression>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& processors, uint8_t sector, int8_t dtype, int8_t withMC, uint32_t batchStart)
304{
305 uint32_t glo_idx = get_global_id(0);
306 auto& clusterer = processors.tpcClusterer[sector];
307 auto& clustererNN = processors.tpcNNClusterer[sector];
308 if (glo_idx >= (uint32_t)clustererNN.mNnClusterizerBatchedMode) {
309 return;
310 }
311
312 uint32_t maxClusterNum = clusterer.mPmemory->counters.nClusters;
313 uint32_t full_glo_idx = glo_idx + batchStart;
314 int32_t model_output_index = glo_idx * clustererNN.mNnClusterizerModelReg1NumOutputNodes;
315
316 CfArray2D<PackedCharge> chargeMap(reinterpret_cast<PackedCharge*>(clusterer.mPchargeMap));
317 uint32_t peakIndex = CAMath::Min(full_glo_idx, maxClusterNum - 1);
318 CfChargePos peak = clusterer.mPfilteredPeakPositions[peakIndex];
319 float central_charge = static_cast<float>(chargeMap[peak].unpack());
320
321 CPU_ONLY(MCLabelAccumulator labelAccElem(clusterer));
322 MCLabelAccumulator* labelAcc = CPU_PTR(&labelAccElem);
323
324 if (full_glo_idx >= maxClusterNum) {
325 if (withMC) {
326 ClusterAccumulator dummy_pc;
327 CPU_ONLY(labelAcc->collect(peak, central_charge));
328 GPUTPCCFClusterizer::buildCluster(
329 clusterer.Param().rec,
330 chargeMap,
331 peak,
332 smem.posBcast,
333 smem.buf,
334 smem.innerAboveThreshold,
335 &dummy_pc,
336 labelAcc);
337 }
338 return;
339 }
340
341 tpc::ClusterNative* clusterOut = clusterer.mPclusterByRow;
342
344
345 if (withMC) {
346 ClusterAccumulator dummy_pc;
347 CPU_ONLY(labelAcc->collect(peak, central_charge));
348 GPUTPCCFClusterizer::buildCluster(
349 clusterer.Param().rec,
350 chargeMap,
351 peak,
352 smem.posBcast,
353 smem.buf,
354 smem.innerAboveThreshold,
355 &dummy_pc,
356 labelAcc);
357 }
358 if ((clusterer.mPmemory->fragment).isOverlap(peak.time())) {
359 if (clusterer.mPclusterPosInRow) {
360 clusterer.mPclusterPosInRow[full_glo_idx] = clusterer.mNMaxClusterPerRow;
361 }
362 return;
363 }
364
365 bool notSinglePad = false, notSingleTime = false;
366 for (uint16_t i = 0; i < 8; i++) {
367 Delta2 d = cfconsts::InnerNeighbors[i];
368 CfChargePos tmp_pos = peak.delta(d);
369 float v = static_cast<float>(chargeMap[tmp_pos].unpack());
370 notSinglePad |= (d.x != 0) && (v > 0.f);
371 notSingleTime |= (d.y != 0) && (v > 0.f);
372 }
373
374 float publishPadPosition = 0.f, publishTimePosition = 0.f;
375 if (dtype == 0) {
376 publishPadPosition = static_cast<float>(peak.pad()) + clustererNN.mOutputDataReg1_32[model_output_index];
377 publishTimePosition = static_cast<float>(peak.time()) + clustererNN.mOutputDataReg1_32[model_output_index + 1];
378 isBoundaryPublish(full_glo_idx, static_cast<int32_t>(peak.row()), publishPadPosition, publishTimePosition);
379 pc.setFull(central_charge * clustererNN.mOutputDataReg1_32[model_output_index + 4],
380 publishPadPosition,
381 notSinglePad ? clustererNN.mOutputDataReg1_32[model_output_index + 2] : 0.f,
382 (clusterer.mPmemory->fragment).start + publishTimePosition,
383 notSingleTime ? clustererNN.mOutputDataReg1_32[model_output_index + 3] : 0.f,
384 clustererNN.mClusterFlags[2 * glo_idx],
385 clustererNN.mClusterFlags[2 * glo_idx + 1]);
386 } else {
387 publishPadPosition = static_cast<float>(peak.pad()) + clustererNN.mOutputDataReg1_16[model_output_index].ToFloat();
388 publishTimePosition = static_cast<float>(peak.time()) + clustererNN.mOutputDataReg1_16[model_output_index + 1].ToFloat();
389 isBoundaryPublish(full_glo_idx, static_cast<int32_t>(peak.row()), publishPadPosition, publishTimePosition);
390 pc.setFull(central_charge * clustererNN.mOutputDataReg1_16[model_output_index + 4].ToFloat(),
391 publishPadPosition,
392 notSinglePad ? clustererNN.mOutputDataReg1_16[model_output_index + 2].ToFloat() : 0.f,
393 (clusterer.mPmemory->fragment).start + publishTimePosition,
394 notSingleTime ? clustererNN.mOutputDataReg1_16[model_output_index + 3].ToFloat() : 0.f,
395 clustererNN.mClusterFlags[2 * glo_idx],
396 clustererNN.mClusterFlags[2 * glo_idx + 1]);
397 }
398
399 // if (boundaryFlag != 0) { // Prints the entire NN input for the given index
400 // // Build a simple buffer manually (float with 3 decimals)
401 // const int MAX_CHARS = 4096;
402 // char buffer[MAX_CHARS];
403 // int pos = 0;
404 //
405 // auto appendChar = [&](char c) {
406 // if (pos < MAX_CHARS - 1) buffer[pos++] = c;
407 // };
408 // auto appendStr = [&](const char* s) {
409 // while (*s && pos < MAX_CHARS - 1) buffer[pos++] = *s++;
410 // };
411 // auto appendUInt = [&](uint32_t v) {
412 // char tmp[16]; int tp = 0;
413 // if (v == 0) { appendChar('0'); return; }
414 // while (v && tp < 16) { tmp[tp++] = char('0' + (v % 10)); v /= 10; }
415 // while (tp--) appendChar(tmp[tp]);
416 // };
417 // auto appendInt = [&](int v) {
418 // if (v < 0) { appendChar('-'); v = -v; }
419 // appendUInt((uint32_t)v);
420 // };
421 // auto appendFloat = [&](float f) {
422 // if (f < 0) { appendChar('-'); f = -f; }
423 // int ip = (int)f;
424 // float frac = f - (float)ip;
425 // appendInt(ip);
426 // appendChar('.');
427 // for (int i = 0; i < 3; i++) {
428 // frac *= 10.f;
429 // int d = (int)frac;
430 // appendChar((char)('0' + (d < 0 ? 0 : (d > 9 ? 9 : d))));
431 // frac -= d;
432 // if (frac < 0) frac = 0;
433 // }
434 // };
435 //
436 // appendStr("(NN CLUS) DEBUG: Boundary cluster detected (sector ");
437 // appendUInt(sector);
438 // appendStr(", row ");
439 // appendUInt(peak.row());
440 // appendStr(", pad ");
441 // appendFloat(publishPadPosition);
442 // appendStr(", time ");
443 // appendFloat(publishTimePosition);
444 // appendStr(") [glo_idx=");
445 // appendUInt(glo_idx);
446 // appendStr(" elemSize=");
447 // appendInt(clustererNN.mNnClusterizerElementSize);
448 // appendStr(" dtype=");
449 // appendInt(dtype);
450 // appendStr("] INPUT:");
451 //
452 // int elemSize = clustererNN.mNnClusterizerElementSize;
453 // int baseIdx = glo_idx * elemSize;
454 //
455 // int maxPrint = elemSize;
456 // for (int i = 0; i < maxPrint; ++i) {
457 // appendChar(' ');
458 // float v = (dtype == 0) ? clustererNN.mInputData_16[baseIdx + i].ToFloat()
459 // : clustererNN.mInputData_32[baseIdx + i];
460 // appendFloat(v);
461 // if (pos > (MAX_CHARS - 32)) { appendStr(" ..."); break; }
462 // }
463 //
464 // buffer[pos] = 0;
465 // printf("%s\n", buffer);
466 // }
467
468 tpc::ClusterNative myCluster;
469 bool rejectCluster = !pc.toNative(peak, central_charge, myCluster, clusterer.Param(), chargeMap);
470 if (clustererNN.mNnClusterizerUseClassification) {
471 rejectCluster |= (clustererNN.mOutputDataClass[peakIndex] <= 0);
472 }
473 if (rejectCluster) {
474 if (clusterer.mPclusterPosInRow) {
475 clusterer.mPclusterPosInRow[full_glo_idx] = clusterer.mNMaxClusterPerRow;
476 }
477 return;
478 }
479
480 uint32_t rowIndex = 0;
481 if (clusterOut != nullptr) {
482 rowIndex = GPUTPCCFClusterizer::sortIntoBuckets(
483 clusterer,
484 myCluster,
485 peak.row(),
486 clusterer.mNMaxClusterPerRow,
487 clusterer.mPclusterInRow,
488 clusterOut);
489 if (clusterer.mPclusterPosInRow != nullptr) {
490 clusterer.mPclusterPosInRow[full_glo_idx] = rowIndex;
491 }
492 } else if (clusterer.mPclusterPosInRow) {
493 rowIndex = clusterer.mPclusterPosInRow[full_glo_idx];
494 }
495 CPU_ONLY(labelAcc->commit(peak.row(), rowIndex, clusterer.mNMaxClusterPerRow));
496}
497
498template <>
499GPUdii() void GPUTPCNNClusterizerKernels::Thread<GPUTPCNNClusterizerKernels::publishClass2Regression>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& processors, uint8_t sector, int8_t dtype, int8_t withMC, uint32_t batchStart)
500{
501 uint32_t glo_idx = get_global_id(0);
502 auto& clusterer = processors.tpcClusterer[sector];
503 auto& clustererNN = processors.tpcNNClusterer[sector];
504 if (glo_idx >= (uint32_t)clustererNN.mNnClusterizerBatchedMode) {
505 return;
506 }
507
508 uint32_t maxClusterNum = clusterer.mPmemory->counters.nClusters;
509 CfArray2D<PackedCharge> chargeMap(reinterpret_cast<PackedCharge*>(clusterer.mPchargeMap));
510 CfChargePos peak = clusterer.mPfilteredPeakPositions[CAMath::Min(glo_idx + batchStart, (uint32_t)(clusterer.mPmemory->counters.nClusters - 1))];
511 float central_charge = static_cast<float>(chargeMap[peak].unpack());
512
513 CPU_ONLY(MCLabelAccumulator labelAccElem(clusterer));
514 MCLabelAccumulator* labelAcc = CPU_PTR(&labelAccElem);
515 tpc::ClusterNative* clusterOut = clusterer.mPclusterByRow;
516 uint32_t full_glo_idx = glo_idx + batchStart;
517
518 if (full_glo_idx >= maxClusterNum) {
519 if (withMC) {
520 ClusterAccumulator dummy_pc;
521 CPU_ONLY(labelAcc->collect(peak, central_charge));
522 GPUTPCCFClusterizer::buildCluster(
523 clusterer.Param().rec,
524 chargeMap,
525 peak,
526 smem.posBcast,
527 smem.buf,
528 smem.innerAboveThreshold,
529 &dummy_pc,
530 labelAcc);
531 }
532 return;
533 }
534
535 uint32_t model_output_index = glo_idx * clustererNN.mNnClusterizerModelReg2NumOutputNodes;
536
538
539 if (withMC) {
540 ClusterAccumulator dummy_pc;
541 CPU_ONLY(labelAcc->collect(peak, central_charge));
542 GPUTPCCFClusterizer::buildCluster(
543 clusterer.Param().rec,
544 chargeMap,
545 peak,
546 smem.posBcast,
547 smem.buf,
548 smem.innerAboveThreshold,
549 &dummy_pc,
550 labelAcc);
551 }
552 if ((clusterer.mPmemory->fragment).isOverlap(peak.time())) {
553 if (clusterer.mPclusterPosInRow) {
554 clusterer.mPclusterPosInRow[full_glo_idx] = clusterer.mNMaxClusterPerRow;
555 }
556 return;
557 }
558
559 // Cluster 1
560 float publishPadPosition = 0.f, publishTimePosition = 0.f;
561 if (dtype == 0) {
562 publishPadPosition = static_cast<float>(peak.pad()) + clustererNN.mOutputDataReg2_32[model_output_index];
563 publishTimePosition = static_cast<float>(peak.time()) + clustererNN.mOutputDataReg2_32[model_output_index + 1];
564 isBoundaryPublish(full_glo_idx, static_cast<int32_t>(peak.row()), publishPadPosition, publishTimePosition);
565 pc.setFull(central_charge * clustererNN.mOutputDataReg2_32[model_output_index + 8],
566 publishPadPosition,
567 clustererNN.mOutputDataReg2_32[model_output_index + 4],
568 (clusterer.mPmemory->fragment).start + publishTimePosition,
569 clustererNN.mOutputDataReg2_32[model_output_index + 6],
570 clustererNN.mClusterFlags[2 * glo_idx],
571 clustererNN.mClusterFlags[2 * glo_idx + 1]);
572 } else {
573 publishPadPosition = static_cast<float>(peak.pad()) + clustererNN.mOutputDataReg2_16[model_output_index].ToFloat();
574 publishTimePosition = static_cast<float>(peak.time()) + clustererNN.mOutputDataReg2_16[model_output_index + 1].ToFloat();
575 isBoundaryPublish(full_glo_idx, static_cast<int32_t>(peak.row()), publishPadPosition, publishTimePosition);
576 pc.setFull(central_charge * clustererNN.mOutputDataReg2_16[model_output_index + 8].ToFloat(),
577 publishPadPosition,
578 clustererNN.mOutputDataReg2_16[model_output_index + 4].ToFloat(),
579 (clusterer.mPmemory->fragment).start + publishTimePosition,
580 clustererNN.mOutputDataReg2_16[model_output_index + 6].ToFloat(),
581 clustererNN.mClusterFlags[2 * glo_idx],
582 clustererNN.mClusterFlags[2 * glo_idx + 1]);
583 }
584
585 tpc::ClusterNative myCluster;
586 bool rejectCluster = !pc.toNative(peak, central_charge, myCluster, clusterer.Param(), chargeMap);
587 if (clustererNN.mNnClusterizerUseClassification) {
588 rejectCluster |= (clustererNN.mOutputDataClass[CAMath::Min(full_glo_idx, (uint32_t)clusterer.mPmemory->counters.nClusters - 1)] <= 0);
589 }
590 if (rejectCluster) {
591 if (clusterer.mPclusterPosInRow) {
592 clusterer.mPclusterPosInRow[full_glo_idx] = clusterer.mNMaxClusterPerRow;
593 }
594 return;
595 }
596
597 uint32_t rowIndex = 0;
598 if (clusterOut != nullptr) {
599 rowIndex = GPUTPCCFClusterizer::sortIntoBuckets(
600 clusterer,
601 myCluster,
602 peak.row(),
603 clusterer.mNMaxClusterPerRow,
604 clusterer.mPclusterInRow,
605 clusterOut);
606 if (clusterer.mPclusterPosInRow != nullptr) {
607 clusterer.mPclusterPosInRow[full_glo_idx] = rowIndex;
608 }
609 } else if (clusterer.mPclusterPosInRow) {
610 rowIndex = clusterer.mPclusterPosInRow[full_glo_idx];
611 }
612 CPU_ONLY(labelAcc->commit(peak.row(), rowIndex, clusterer.mNMaxClusterPerRow));
613
614 // Cluster 2
615 if (dtype == 0) {
616 publishPadPosition = static_cast<float>(peak.pad()) + clustererNN.mOutputDataReg2_32[model_output_index + 1];
617 publishTimePosition = static_cast<float>(peak.time()) + clustererNN.mOutputDataReg2_32[model_output_index + 3];
618 isBoundaryPublish(full_glo_idx, static_cast<int32_t>(peak.row()), publishPadPosition, publishTimePosition);
619 pc.setFull(central_charge * clustererNN.mOutputDataReg2_32[model_output_index + 9],
620 publishPadPosition,
621 clustererNN.mOutputDataReg2_32[model_output_index + 5],
622 (clusterer.mPmemory->fragment).start + publishTimePosition,
623 clustererNN.mOutputDataReg2_32[model_output_index + 7],
624 clustererNN.mClusterFlags[2 * glo_idx],
625 clustererNN.mClusterFlags[2 * glo_idx + 1]);
626 } else {
627 publishPadPosition = static_cast<float>(peak.pad()) + clustererNN.mOutputDataReg2_16[model_output_index + 1].ToFloat();
628 publishTimePosition = static_cast<float>(peak.time()) + clustererNN.mOutputDataReg2_16[model_output_index + 3].ToFloat();
629 isBoundaryPublish(full_glo_idx, static_cast<int32_t>(peak.row()), publishPadPosition, publishTimePosition);
630 pc.setFull(central_charge * clustererNN.mOutputDataReg2_16[model_output_index + 9].ToFloat(),
631 publishPadPosition,
632 clustererNN.mOutputDataReg2_16[model_output_index + 5].ToFloat(),
633 (clusterer.mPmemory->fragment).start + publishTimePosition,
634 clustererNN.mOutputDataReg2_16[model_output_index + 7].ToFloat(),
635 clustererNN.mClusterFlags[2 * glo_idx],
636 clustererNN.mClusterFlags[2 * glo_idx + 1]);
637 }
638
639 rejectCluster = !pc.toNative(peak, central_charge, myCluster, clusterer.Param(), chargeMap);
640 if (clustererNN.mNnClusterizerUseClassification) {
641 rejectCluster |= (clustererNN.mOutputDataClass[CAMath::Min(full_glo_idx, (uint32_t)clusterer.mPmemory->counters.nClusters - 1)] <= 0);
642 }
643 if (rejectCluster) {
644 if (clusterer.mPclusterPosInRow) {
645 clusterer.mPclusterPosInRow[full_glo_idx] = clusterer.mNMaxClusterPerRow;
646 }
647 return;
648 }
649
650 if (clusterOut != nullptr) {
651 rowIndex = GPUTPCCFClusterizer::sortIntoBuckets(
652 clusterer,
653 myCluster,
654 peak.row(),
655 clusterer.mNMaxClusterPerRow,
656 clusterer.mPclusterInRow,
657 clusterOut);
658 if (clusterer.mPclusterPosInRow != nullptr) {
659 clusterer.mPclusterPosInRow[full_glo_idx] = rowIndex;
660 }
661 } else if (clusterer.mPclusterPosInRow) {
662 rowIndex = clusterer.mPclusterPosInRow[full_glo_idx];
663 }
664 // CPU_ONLY(labelAcc->commit(peak.row(), rowIndex, clusterer.mNMaxClusterPerRow)); // -> Is this needed? How to handle MC labels for split clusters?
665}
666
667// ---------------------------------
668template <>
669GPUdii() void GPUTPCNNClusterizerKernels::Thread<GPUTPCNNClusterizerKernels::publishDeconvolutionFlags>(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& processors, uint8_t sector, int8_t dtype, int8_t withMC, uint batchStart)
670{
671 // Implements identical publishing logic as the heuristic clusterizer and deconvolution kernel
672 uint32_t glo_idx = get_global_id(0);
673 auto& clusterer = processors.tpcClusterer[sector];
674 auto& clustererNN = processors.tpcNNClusterer[sector];
675 if (glo_idx + batchStart >= clusterer.mPmemory->counters.nClusters || glo_idx >= (uint32_t)clustererNN.mNnClusterizerBatchedMode) {
676 return;
677 }
678 CfArray2D<PackedCharge> chargeMap(reinterpret_cast<PackedCharge*>(clusterer.mPchargeMap));
679 CfChargePos peak = clusterer.mPfilteredPeakPositions[glo_idx + batchStart];
680
681 clustererNN.mClusterFlags[2 * glo_idx] = 0;
682 clustererNN.mClusterFlags[2 * glo_idx + 1] = 0;
683 for (int i = 0; i < 8; i++) {
684 Delta2 d = cfconsts::InnerNeighbors[i];
685 CfChargePos tmp_pos = peak.delta(d);
686 PackedCharge charge = chargeMap[tmp_pos];
687 clustererNN.mClusterFlags[2 * glo_idx] += (d.y != 0 && charge.isSplit());
688 clustererNN.mClusterFlags[2 * glo_idx + 1] += (d.x != 0 && charge.isSplit());
689 }
690 for (int i = 0; i < 16; i++) {
691 Delta2 d = cfconsts::OuterNeighbors[i];
692 CfChargePos tmp_pos = peak.delta(d);
693 PackedCharge charge = chargeMap[tmp_pos];
694 clustererNN.mClusterFlags[2 * glo_idx] += (d.y != 0 && charge.isSplit() && !charge.has3x3Peak());
695 clustererNN.mClusterFlags[2 * glo_idx + 1] += (d.x != 0 && charge.isSplit() && !charge.has3x3Peak());
696 }
697}
698
699// THe following arithmetic is done because the network is trained with a split between IROC and OROC boundary
700GPUd() int32_t GPUTPCNNClusterizerKernels::padOffset(int32_t row_ref, int32_t row_current)
701{
702 if (row_current < 0 || row_current >= o2::tpc::constants::MAXGLOBALPADROW) {
703 return 0; // Short-circuit for out-of-bound rows
704 } else {
705 return (int)((GPUTPCGeometry::NPads(row_current) - GPUTPCGeometry::NPads(row_ref)) / 2);
706 }
707}
708
709GPUd() int32_t GPUTPCNNClusterizerKernels::rowOffset(int32_t row, int32_t offset)
710{
711 return (row > 62 ? offset : 0);
712}
713
714GPUd() bool GPUTPCNNClusterizerKernels::isBoundary(int32_t row, int32_t pad, int32_t maxrow, int32_t iroc_row, int32_t npads_row, int32_t npads_reference)
715{
716 if (pad < 0) { // Faster short-circuit
717 return true;
718 } else if (row < 63) {
719 return (pad >= npads_row);
720 } else if (row < iroc_row) { // to account for the gap between IROC and OROC. Charge will be set to the boundary fill value in order to signal boundaries to the neural network
721 return true;
722 } else if (row < maxrow) {
723 return (pad >= npads_reference);
724 } else {
725 return true;
726 }
727}
728
729GPUd() bool GPUTPCNNClusterizerKernels::isBoundaryPublish(int32_t idx, int32_t row, float& pad, float& time)
730{
731 if (pad < 0) {
732 // printf("(NN CLUS) WARNING: Boundary detected, idx = %d, pad < 0: row %d, pad %f (%d, %d), time %f (%d, %d)\n", idx, row, pad, 0, static_cast<int>(GPUTPCGeometry::NPads(row)), time, 0, TPC_MAX_FRAGMENT_LEN_GPU);
733 pad = 0.f;
734 return true;
735 } else if (pad >= static_cast<int>(GPUTPCGeometry::NPads(row))) {
736 // printf("(NN CLUS) WARNING: Boundary detected, idx = %d, pad >= static_cast<int>(GPUTPCGeometry::NPads(row): row %d, pad %f (%d, %d), time %f (%d, %d)\n", idx, row, pad, 0, static_cast<int>(GPUTPCGeometry::NPads(row)), time, 0, TPC_MAX_FRAGMENT_LEN_GPU);
737 pad = static_cast<float>(GPUTPCGeometry::NPads(row) - 1);
738 return true;
739 } else if (time < 0) {
740 // printf("(NN CLUS) WARNING: Boundary detected, idx = %d, time < 0: row %d, pad %f (%d, %d), time %f (%d, %d)\n", idx, row, pad, 0, static_cast<int>(GPUTPCGeometry::NPads(row)), time, 0, TPC_MAX_FRAGMENT_LEN_GPU);
741 time = 0.f;
742 return true;
743 } else if (time >= TPC_MAX_FRAGMENT_LEN_GPU) {
744 // printf("(NN CLUS) WARNING: Boundary detected, idx = %d, time >= TPC_MAX_FRAGMENT_LEN_GPU: row %d, pad %f (%d, %d), time %f (%d, %d)\n", idx, row, pad, 0, static_cast<int>(GPUTPCGeometry::NPads(row)), time, 0, TPC_MAX_FRAGMENT_LEN_GPU);
745 time = static_cast<float>(TPC_MAX_FRAGMENT_LEN_GPU - 1);
746 return true;
747 } else {
748 return false;
749 }
750}
int16_t charge
Definition RawEventData.h:5
int16_t time
Definition RawEventData.h:4
int32_t i
#define get_local_size(dim)
#define get_local_id(dim)
#define get_num_groups(dim)
#define get_global_id(dim)
#define get_group_id(dim)
GPUdii() void GPUTPCNNClusterizerKernels
GPUd() int32_t GPUTPCNNClusterizerKernels
uint16_t pos
Definition RawData.h:3
static constexpr size_t SCRATCH_PAD_WORK_GROUP_SIZE
void collect(const CfChargePos &pos, float q)
void commit(tpccf::Row row, uint32_t indexInRow, uint32_t maxElemsPerBucket)
#define TPC_MAX_FRAGMENT_LEN_GPU
#define CPU_ONLY(x)
#define CPU_PTR(x)
const GLdouble * v
Definition glcorearb.h:832
GLintptr offset
Definition glcorearb.h:660
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLuint start
Definition glcorearb.h:469
constexpr int MAXSECTOR
Definition Constants.h:28
constexpr int MAXGLOBALPADROW
Definition Constants.h:34
int16_t y
int16_t x
std::vector< int > row