From 6f7877acf983cf952c661bebe0df91a3431cc857 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Thu, 5 Nov 2026 21:02:41 +0100 Subject: [PATCH 1/7] GPU Metal: wait for and record the events of kernels and copies The kernels, the memory clear, GPUMemCpy and WriteToConstantMemory ignored the events they were given, which is how the chain orders the work of its lanes across streams. Wait for the event list before and record the event after, as the CUDA backend does. --- GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm | 9 +++++++++ .../Base/metal/GPUReconstructionMetalKernels.mm | 7 +++++++ .../metal/GPUReconstructionMetalKernelsSpecialize.inc | 6 ++++++ 3 files changed, 22 insertions(+) diff --git a/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm b/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm index 00cd5d8687624..ed2805d0579fb 100644 --- a/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm +++ b/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm @@ -232,6 +232,9 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const } auto realStream = stream == -1 ? 0 : stream; + if (nEvents) { + StreamWaitForEvents(realStream, evList, nEvents); + } const MetalMemRef s = metalResolve(mInternals, src, mHostMemoryBase, mHostMemorySize, mDeviceMemoryBase, mDeviceMemorySize); const MetalMemRef d = metalResolve(mInternals, dst, mHostMemoryBase, mHostMemorySize, mDeviceMemoryBase, mDeviceMemorySize); @@ -252,6 +255,9 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const [blit endEncoding]; CommitStream(realStream); } + if (ev) { + RecordMarker(ev, realStream); + } if (GetProcessingSettings().serializeGPU & 2) { GPUDebug(("GPUMemCpy " + std::to_string(toGPU)).c_str(), stream, true); @@ -282,6 +288,9 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const [blit endEncoding]; CommitStream(realStream); } + if (ev) { + RecordMarker(ev, realStream); + } if (GetProcessingSettings().serializeGPU & 2) { GPUDebug("WriteToConstantMemory", stream, true); diff --git a/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernels.mm b/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernels.mm index e4a858220588b..ed70217a9690d 100644 --- a/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernels.mm +++ b/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernels.mm @@ -25,6 +25,10 @@ const uint32_t num = GetKernelNum(); auto& kExec = _xyz.x; auto& runRange = _xyz.y; + auto& kEvents = _xyz.z; + if (kEvents.evList) { + StreamWaitForEvents(kExec.stream, kEvents.evList, kEvents.nEvents); + } id pso = mInternals->pipelines[num]; id computeEncoder = [mInternals->commandBuffers[kExec.stream] computeCommandEncoder]; @@ -51,6 +55,9 @@ // without committing the kernel never reaches the device. [computeEncoder endEncoding]; CommitStream(kExec.stream); + if (kEvents.ev) { + RecordMarker(kEvents.ev, kExec.stream); + } } template diff --git a/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernelsSpecialize.inc b/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernelsSpecialize.inc index 7c2abe7cf8954..3fae4727566ca 100644 --- a/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernelsSpecialize.inc +++ b/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernelsSpecialize.inc @@ -16,6 +16,9 @@ inline void GPUReconstructionMetal::runKernelBackend(const krn { const uint64_t offset = static_cast(ptr) - static_cast(mDeviceMemoryBase); const uint64_t length = (size + 15ull) & ~15ull; + if (_xyz.z.evList) { + StreamWaitForEvents(_xyz.x.stream, _xyz.z.evList, _xyz.z.nEvents); + } id cb = mInternals->commandBuffers[_xyz.x.stream]; id blit = [cb blitCommandEncoder]; @@ -24,4 +27,7 @@ inline void GPUReconstructionMetal::runKernelBackend(const krn [blit endEncoding]; CommitStream(_xyz.x.stream); + if (_xyz.z.ev) { + RecordMarker(_xyz.z.ev, _xyz.x.stream); + } } From 83567f009c57d62ac69751968a7f186bbb621f5b Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Thu, 5 Nov 2026 21:02:41 +0100 Subject: [PATCH 2/7] GPU Metal: run the work of a stream in order Consecutive command buffers of one Metal queue are only ordered as far as the hazard tracking of the bound buffers sees, and the kernels reach most memory through addresses stored in constant memory. A kernel could thus start before the one before it on the same stream had finished: on a 5-event pp time frame the cluster finder found no clusters, and between 12,125 and 12,142 instead of 19,009 with every kernel synchronised. Make each command buffer of a stream wait on a per-stream event that the previous one signals, which gives a CUDA stream's in-order semantics without blocking the host. --- GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm | 9 +++++++++ .../Base/metal/GPUReconstructionMetalIncludesHost.h | 3 +++ 2 files changed, 12 insertions(+) diff --git a/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm b/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm index ed2805d0579fb..fdf4321d14a5d 100644 --- a/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm +++ b/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm @@ -105,6 +105,8 @@ if (mInternals->commandBuffers[i] == nil) { GPUErrorReturn("Error creating Metal command buffer"); } + mInternals->streamOrder[i] = [mInternals->device newSharedEvent]; + mInternals->streamOrderValue[i] = 0; } mInternals->mem_host = [mInternals->device newBufferWithLength:mHostMemorySize options:MTLResourceStorageModeShared]; @@ -179,6 +181,7 @@ DrainStream(i); [mInternals->commandBuffers[i] release]; [mInternals->commandQueues[i] release]; + [mInternals->streamOrder[i] release]; } [mInternals->mem_host release]; } @@ -323,11 +326,17 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const void GPUReconstructionMetal::CommitStream(int32_t stream) { id cb = mInternals->commandBuffers[stream]; + // A CUDA stream runs its work in order, a Metal queue only as far as its + // hazard tracking sees, and the kernels reach most memory through addresses + // in constant memory. So each buffer of a stream waits for the previous one. + const uint64_t value = ++mInternals->streamOrderValue[stream]; + [cb encodeSignalEvent:mInternals->streamOrder[stream] value:value]; [cb commit]; // Hand the committed buffer over to the stream's submission list, which owns // it until it is waited for, and open a fresh one to encode the next work in. mInternals->submittedBuffers[stream].emplace_back(cb); mInternals->commandBuffers[stream] = [[mInternals->commandQueues[stream] commandBuffer] retain]; + [mInternals->commandBuffers[stream] encodeWaitForEvent:mInternals->streamOrder[stream] value:value]; } void GPUReconstructionMetal::DrainStream(int32_t stream) diff --git a/GPU/GPUTracking/Base/metal/GPUReconstructionMetalIncludesHost.h b/GPU/GPUTracking/Base/metal/GPUReconstructionMetalIncludesHost.h index 9c32ba0dc4bae..a716b75107cc8 100644 --- a/GPU/GPUTracking/Base/metal/GPUReconstructionMetalIncludesHost.h +++ b/GPU/GPUTracking/Base/metal/GPUReconstructionMetalIncludesHost.h @@ -59,6 +59,9 @@ struct GPUReconstructionMetalInternals { // encoded into afterwards, so the two cannot be the same object. std::array, constants::GPU_MAX_STREAMS> commandBuffers; std::array>, constants::GPU_MAX_STREAMS> submittedBuffers; + // Signalled by each committed buffer and waited for by the next one, see CommitStream + std::array, constants::GPU_MAX_STREAMS> streamOrder; + std::array streamOrderValue; std::vector> functions; std::vector> pipelines; From 89d86008f8c2c6d37948fc96245c19acab1d2b70 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Thu, 5 Nov 2026 21:02:41 +0100 Subject: [PATCH 3/7] GPU Metal: size the automatic grids by the number of GPU cores The multiprocessor count, of which the automatically sized grids launch minBlocks blocks each, was never set and stayed 1, so those kernels ran one or a few threadgroups on a 32-core M1 Max. Metal does not report the core count; read gpu-core-count from the GPU's IORegistry entry, as CUDA uses multiProcessorCount, and link IOKit for that. --- GPU/GPUTracking/Base/metal/CMakeLists.txt | 2 +- .../Base/metal/GPUReconstructionMetal.mm | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/GPU/GPUTracking/Base/metal/CMakeLists.txt b/GPU/GPUTracking/Base/metal/CMakeLists.txt index 9c9b7e4fd2b15..2073819c026ee 100644 --- a/GPU/GPUTracking/Base/metal/CMakeLists.txt +++ b/GPU/GPUTracking/Base/metal/CMakeLists.txt @@ -46,7 +46,7 @@ if(ALIGPU_BUILD_TYPE STREQUAL "O2") PUBLIC_LINK_LIBRARIES O2::GPUTracking TARGETVARNAME targetName) - target_link_libraries(${targetName} PUBLIC ${METAL_FRAMEWORKS}) + target_link_libraries(${targetName} PUBLIC ${METAL_FRAMEWORKS} "-framework IOKit") target_compile_definitions(${targetName} PRIVATE $) # the compile_defitions are not propagated automatically on purpose (they are diff --git a/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm b/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm index fdf4321d14a5d..b543f5d636cc3 100644 --- a/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm +++ b/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm @@ -18,6 +18,7 @@ #include #include +#include #include #include #include // _mh_execute_header @@ -28,6 +29,28 @@ return (1); \ } +namespace +{ +// Metal does not report the number of GPU cores, but the IORegistry entry of +// an Apple silicon GPU does. 0 if it is not there. +uint32_t metalGPUCoreCount() +{ + uint32_t n = 0; + io_service_t service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("AGXAccelerator")); + if (service) { + CFTypeRef p = IORegistryEntrySearchCFProperty(service, kIOServicePlane, CFSTR("gpu-core-count"), kCFAllocatorDefault, kIORegistryIterateRecursively); + if (p) { + if (CFGetTypeID(p) == CFNumberGetTypeID()) { + CFNumberGetValue((CFNumberRef)p, kCFNumberSInt32Type, &n); + } + CFRelease(p); + } + IOObjectRelease(service); + } + return n; +} +} // namespace + #include "utils/qGetLdBinarySymbols.h" QGET_LD_BINARY_SYMBOLS(GPUReconstructionMetalCode_src); @@ -74,6 +97,14 @@ mDeviceName = device_name.c_str(); // Basically a random number for now. mMaxBackendThreads = 1000; + // The automatic grids are minBlocks per core; with the default of 1 they + // left all but one core idle. + mMultiprocessorCount = metalGPUCoreCount(); + if (mMultiprocessorCount == 0) { + GPUWarning("Could not read the number of GPU cores, assuming 1"); + mMultiprocessorCount = 1; + } + mMaxBackendThreads = std::max(mMaxBackendThreads, deviceMaxWorkGroup.width * mMultiprocessorCount); if (GetMetalPrograms()) { return 1; From fe42343f3672de6c582efd44e4b1a0f3eb6a60cb Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Thu, 5 Nov 2026 21:02:41 +0100 Subject: [PATCH 4/7] GPU Metal: collect the work of a stream in one command buffer Every kernel, memory clear and copy was a command buffer of its own, 2,689 per event on a 5-event pp time frame, each committed at once and, since they run in order, each waiting for the previous one's event. Keep encoding into the stream's open buffer instead, ordered by a per-stream MTLFence between encoders, and commit it only where something has to wait for it: draining the stream, recording an event, waiting for events. This makes 535 command buffers per event, and the time per event about 3 to 6% less on an M1 Max. --- .../Base/metal/GPUReconstructionMetal.mm | 15 +++++++++++---- .../metal/GPUReconstructionMetalIncludesHost.h | 18 ++++++++++++++++++ .../metal/GPUReconstructionMetalKernels.mm | 6 ++---- ...GPUReconstructionMetalKernelsSpecialize.inc | 5 ++--- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm b/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm index b543f5d636cc3..26d7281439c92 100644 --- a/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm +++ b/GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm @@ -138,6 +138,8 @@ uint32_t metalGPUCoreCount() } mInternals->streamOrder[i] = [mInternals->device newSharedEvent]; mInternals->streamOrderValue[i] = 0; + mInternals->streamFence[i] = [mInternals->device newFence]; + mInternals->streamHasWork[i] = false; } mInternals->mem_host = [mInternals->device newBufferWithLength:mHostMemorySize options:MTLResourceStorageModeShared]; @@ -213,6 +215,7 @@ uint32_t metalGPUCoreCount() [mInternals->commandBuffers[i] release]; [mInternals->commandQueues[i] release]; [mInternals->streamOrder[i] release]; + [mInternals->streamFence[i] release]; } [mInternals->mem_host release]; } @@ -281,13 +284,13 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const } else { id cb = mInternals->commandBuffers[realStream]; id blit = [cb blitCommandEncoder]; + metalBeginEncoder(mInternals, blit, realStream); [blit copyFromBuffer:s.buffer sourceOffset:s.offset toBuffer:d.buffer destinationOffset:d.offset size:sizeBytes]; - [blit endEncoding]; - CommitStream(realStream); + metalEndEncoder(mInternals, blit, realStream); } if (ev) { RecordMarker(ev, realStream); @@ -314,13 +317,13 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const } else { id cb = mInternals->commandBuffers[realStream]; id blit = [cb blitCommandEncoder]; + metalBeginEncoder(mInternals, blit, realStream); [blit copyFromBuffer:s.buffer sourceOffset:s.offset toBuffer:mInternals->mem_constant destinationOffset:offset size:size]; - [blit endEncoding]; - CommitStream(realStream); + metalEndEncoder(mInternals, blit, realStream); } if (ev) { RecordMarker(ev, realStream); @@ -366,12 +369,16 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const // Hand the committed buffer over to the stream's submission list, which owns // it until it is waited for, and open a fresh one to encode the next work in. mInternals->submittedBuffers[stream].emplace_back(cb); + mInternals->streamHasWork[stream] = false; mInternals->commandBuffers[stream] = [[mInternals->commandQueues[stream] commandBuffer] retain]; [mInternals->commandBuffers[stream] encodeWaitForEvent:mInternals->streamOrder[stream] value:value]; } void GPUReconstructionMetal::DrainStream(int32_t stream) { + if (mInternals->streamHasWork[stream]) { + CommitStream(stream); + } // Waiting on the buffer that is currently open would block forever: it is not // committed, so it never completes. Only what was submitted can be waited for. for (id cb : mInternals->submittedBuffers[stream]) { diff --git a/GPU/GPUTracking/Base/metal/GPUReconstructionMetalIncludesHost.h b/GPU/GPUTracking/Base/metal/GPUReconstructionMetalIncludesHost.h index a716b75107cc8..76303183ac04c 100644 --- a/GPU/GPUTracking/Base/metal/GPUReconstructionMetalIncludesHost.h +++ b/GPU/GPUTracking/Base/metal/GPUReconstructionMetalIncludesHost.h @@ -62,6 +62,10 @@ struct GPUReconstructionMetalInternals { // Signalled by each committed buffer and waited for by the next one, see CommitStream std::array, constants::GPU_MAX_STREAMS> streamOrder; std::array streamOrderValue; + // Orders the encoders within the open command buffer of a stream, which + // collects the stream's work until something has to wait for it. + std::array, constants::GPU_MAX_STREAMS> streamFence; + std::array streamHasWork; std::vector> functions; std::vector> pipelines; @@ -79,6 +83,20 @@ struct GPUReconstructionMetalInternals { std::unordered_map eventValues; std::mutex eventMutex; }; +// An encoder of a stream starts after the previous one of the same stream and +// leaves its work in the open command buffer. +template +inline void metalBeginEncoder(GPUReconstructionMetalInternals* internals, E encoder, int32_t stream) +{ + [encoder waitForFence:internals->streamFence[stream]]; +} +template +inline void metalEndEncoder(GPUReconstructionMetalInternals* internals, E encoder, int32_t stream) +{ + [encoder updateFence:internals->streamFence[stream]]; + [encoder endEncoding]; + internals->streamHasWork[stream] = true; +} } // namespace o2::gpu #endif // GPURECONSTRUCTIONOMETALINCLUDESHOST_H diff --git a/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernels.mm b/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernels.mm index ed70217a9690d..7d536ef9f01ff 100644 --- a/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernels.mm +++ b/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernels.mm @@ -32,6 +32,7 @@ id pso = mInternals->pipelines[num]; id computeEncoder = [mInternals->commandBuffers[kExec.stream] computeCommandEncoder]; + metalBeginEncoder(mInternals, computeEncoder, kExec.stream); // Slots 0 to 2 are what GPUCA_KRNL_BUFFERS and GPUCA_KRNL_SECTOR_ARG declare // in the generated signature; the kernel's own arguments follow from slot 3. @@ -51,10 +52,7 @@ } [computeEncoder dispatchThreadgroups:MTLSizeMake(kExec.nBlocks, 1, 1) threadsPerThreadgroup:MTLSizeMake(threadGroupSize, 1, 1)]; - // Without ending the encoder the next one on this buffer is rejected, and - // without committing the kernel never reaches the device. - [computeEncoder endEncoding]; - CommitStream(kExec.stream); + metalEndEncoder(mInternals, computeEncoder, kExec.stream); if (kEvents.ev) { RecordMarker(kEvents.ev, kExec.stream); } diff --git a/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernelsSpecialize.inc b/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernelsSpecialize.inc index 3fae4727566ca..5f456a7ce06bb 100644 --- a/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernelsSpecialize.inc +++ b/GPU/GPUTracking/Base/metal/GPUReconstructionMetalKernelsSpecialize.inc @@ -22,11 +22,10 @@ inline void GPUReconstructionMetal::runKernelBackend(const krn id cb = mInternals->commandBuffers[_xyz.x.stream]; id blit = [cb blitCommandEncoder]; + metalBeginEncoder(mInternals, blit, _xyz.x.stream); [blit fillBuffer:mInternals->mem_gpu range:NSMakeRange(offset, length) value:0]; - [blit endEncoding]; - - CommitStream(_xyz.x.stream); + metalEndEncoder(mInternals, blit, _xyz.x.stream); if (_xyz.z.ev) { RecordMarker(_xyz.z.ev, _xyz.x.stream); } From 4f6b89ec9c3f4615c9227ca87f5857fc9e0d8ae9 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Thu, 5 Nov 2026 22:13:32 +0100 Subject: [PATCH 5/7] GPU Metal: pass the no-fast-math flags as separate arguments GPUCA_METAL_NO_FAST_MATH_FLAGS is a string of two flags, appended to METAL_FLAGS as one list element, so the Metal compiler got the single argument '-fno-fast-math -ffp-contract=off' and rejected it: with GPUCA_DETERMINISTIC_MODE set to NO_FAST_MATH or OPTO2 the Metal kernels did not build. Split the string first. --- GPU/GPUTracking/Base/metal/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/GPU/GPUTracking/Base/metal/CMakeLists.txt b/GPU/GPUTracking/Base/metal/CMakeLists.txt index 2073819c026ee..0b8e10d5aa2f8 100644 --- a/GPU/GPUTracking/Base/metal/CMakeLists.txt +++ b/GPU/GPUTracking/Base/metal/CMakeLists.txt @@ -28,7 +28,9 @@ set(METAL_BIN ${CMAKE_CURRENT_BINARY_DIR}/GPUReconstructionMetalCode) # relies on for GPUgeneric() and GPUdDefault(). set(METAL_FLAGS -std=metal4.1 ${GPUCA_METAL_DENORMALS_FLAGS}) if(GPUCA_DETERMINISTIC_MODE GREATER_EQUAL ${GPUCA_DETERMINISTIC_MODE_MAP_NO_FAST_MATH}) - set(METAL_FLAGS ${METAL_FLAGS} ${GPUCA_METAL_NO_FAST_MATH_FLAGS}) + # A string of several flags, which the custom commands below would pass as one + separate_arguments(GPUCA_METAL_NO_FAST_MATH_FLAGS_LIST UNIX_COMMAND "${GPUCA_METAL_NO_FAST_MATH_FLAGS}") + set(METAL_FLAGS ${METAL_FLAGS} ${GPUCA_METAL_NO_FAST_MATH_FLAGS_LIST}) endif() set(METAL_DEFINES "-D$,$-D>" "-I$,EXCLUDE,^/usr/include/?>,$-I>" From d01e221420835fbc4dab897aec7c7695c147263a Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Fri, 6 Nov 2026 04:49:49 +0100 Subject: [PATCH 6/7] GPU: give the TPC compression of unattached clusters more blocks on Metal A tuning of the Metal column of GPUParameters.csv only, which changes nothing for the other backends. The column was empty, so the kernel ran with the default 256 threads and 1 block per GPU core, 32 blocks on an M1 Max. With 8 blocks per core it takes 50 instead of 109 ms per event of GPU time on a 30-collision Pb-Pb time frame, and the whole event 1.339 instead of 1.389 s (--runs 10, 3 interleaved rounds). 2 and 4 blocks are slower, 16 no faster. The other kernels with an automatic grid do not get faster with more blocks. --- GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv b/GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv index e6c9cfd223dcf..25ae6e965cd7c 100644 --- a/GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv +++ b/GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv @@ -16,7 +16,7 @@ GPUTPCCreateOccupancyMap_fill,256,,,,,,,,,256,,256,256,,256,,,256, GPUTPCCreateOccupancyMap_fold,256,,,,,,,,,256,,256,256,,256,,,256, GPUTRDTrackerKernels_o2Version,512,,,,,,,,,512,,512,512,,512,,,512, GPUTPCCompressionKernels_step0attached,256,,"[128, 1]","[64, 2]",,,,,,"[160, 2]",128,"[448, 1]",352,,"[1024, 1]",,"[96, 3]","[128, 4]", -GPUTPCCompressionKernels_step1unattached,256,,"[512, 2]","[512, 2]",,,,,,"[288, 4]","[512, 2]","[256, 4]","[512, 2]",,"[512, 3]",,"[512, 2]","[512, 3]", +GPUTPCCompressionKernels_step1unattached,256,,"[512, 2]","[512, 2]",,,,,,"[288, 4]","[512, 2]","[256, 4]","[512, 2]",,"[512, 3]",,"[512, 2]","[512, 3]","[256, 8]" GPUTPCDecompressionKernels_step0attached,256,,"[128, 2]","[128, 2]",,,,,,"[32, 1]","[32, 1]","[32, 1]","[32, 1]",,"[128, 1]",,"[32, 1]","[128, 1]", GPUTPCDecompressionKernels_step1unattached,256,,"[64, 2]","[64, 2]",,,,,,"[32, 1]","[32, 1]","[32, 1]","[32, 1]",,"[64, 1]",,"[32, 1]","[64, 1]", GPUTPCDecompressionUtilKernels_sortPerSectorRow,256,,,,,,,,,256,,256,256,,256,,,256, From f7b2f5be24f28393a716dfe73a15b1f7126206e6 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Fri, 6 Nov 2026 04:49:49 +0100 Subject: [PATCH 7/7] GPU: smaller thread groups for the merger track fit and the tracking data on Metal A tuning of the Metal column of GPUParameters.csv only, which changes nothing for the other backends. On an M1 Max with a 30-collision Pb-Pb time frame, GPU time per event: GPUTPCGMMergerTrackFit 256: 158.5 ms 128 x 2: 141.6 64 x 4: 120.9 32 x 8: 120.3 GPUTPCCreateTrackingData 256: 44.0 ms 128: 27.3 64: 17.9 32: 19.8 and the whole event 1.331 s before, 1.278 s with [64, 4] and 64 (--runs 10, 3 interleaved rounds, spread under 15 ms). Smaller groups did not help GPUTPCTrackletConstructor (219 ms at 256, 218 at 128, 223 at 64, slower at 192, 512 and 1024), GPUTPCGMMergerSectorRefit (131), GPUTPCExtrapolationTracking (67 to 64) or GPUTPCGMMergerFollowLoopers (34 to 36), which keep the defaults. --- GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv b/GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv index 25ae6e965cd7c..060ae99bffc37 100644 --- a/GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv +++ b/GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv @@ -5,7 +5,7 @@ WARP_SIZE,0,,64,64,32,32,32,32,32,32,32,32,32,32,32,64,32,64,32 THREAD_COUNT_DEFAULT,256,,256,256,,,,,,512,512,,512,256,512,512,512,,256 ,,,,,,,,,,,,,,,,,,, LB:,,,,,,,,,,,,,,,,,,, -GPUTPCCreateTrackingData,256,,"[256, 7]","[192, 2]",,,,,,"[224, 7]",256,"[128, 14]",416,,"[64, 21]",,384,"[320, 2]", +GPUTPCCreateTrackingData,256,,"[256, 7]","[192, 2]",,,,,,"[224, 7]",256,"[128, 14]",416,,"[64, 21]",,384,"[320, 2]",64 GPUTPCTrackletConstructor,256,,"[768, 8]","[512, 10]","[256, 2]","[256, 1]","[256, 2]","[1024, 2]","[512, 4]",1024,"[256, 2]",1024,"[1024, 1]",,"[768, 2]",,768,512, GPUTPCTrackletSelector,256,,"[384, 5]","[192, 10]","[256, 3]","[256, 1]","[256, 3]","[512, 4]","[256, 3]","[288, 3]","[192, 3]","[544, 1]","[32, 2]",,"[384, 3]",,992,"[256, 6]", GPUTPCNeighboursFinder,256,,"[192, 8]","[960, 8]",256,256,256,512,256,864,"[640, 1]","[512, 2]","[736, 1]",,"[480, 3]",,992,"[704, 1]", @@ -27,7 +27,7 @@ GPUTPCCFDecodeZSLink,"""GPUCA_WARP_SIZE""",,"""GPUCA_WARP_SIZE""","""GPUCA_WARP_ GPUTPCCFDecodeZSDenseLink,"""GPUCA_WARP_SIZE""",,"[""GPUCA_WARP_SIZE"", 4]","[""GPUCA_WARP_SIZE"", 14]",,,,,,"[""GPUCA_WARP_SIZE"", 14]","""GPUCA_WARP_SIZE""","[""GPUCA_WARP_SIZE"", 22]","[""GPUCA_WARP_SIZE"", 22]",,"[""GPUCA_WARP_SIZE"", 17]",,"[""GPUCA_WARP_SIZE"", 8]","[""GPUCA_WARP_SIZE"", 5]", GPUTPCCFGather,"[1024, 1]",,"[1024, 5]","[1024, 1]",,,,,,"[160, 11]","[1024, 1]",736,896,,"[928, 1]",,"[1024, 1]","[320, 2]", COMPRESSION_GATHER,1024,,1024,1024,,,,,,1024,1024,,1024,,,,,, -GPUTPCGMMergerTrackFit,256,,"[192, 2]","[64, 7]",,,,,,"[32, 16]","[32, 8]","[32, 14]","[160, 2]",,"[32, 24]",,"[64, 8]","[64, 6]", +GPUTPCGMMergerTrackFit,256,,"[192, 2]","[64, 7]",,,,,,"[32, 16]","[32, 8]","[32, 14]","[160, 2]",,"[32, 24]",,"[64, 8]","[64, 6]","[64, 4]" GPUTPCGMMergerFollowLoopers,256,,"[256, 5]","[256, 4]",,,,,,"[256, 4]","[128, 4]","[1024, 1]",640,,"[128, 16]",,"[224, 3]","[256, 7]", GPUTPCGMMergerSectorRefit,256,,"[64, 4]","[256, 2]",,,,,,"[32, 8]","[64, 5]","[32, 7]","[32, 7]",,"[32, 20]",,"[32, 10]","[64, 4]", GPUTPCGMMergerUnpackResetIds,256,,256,256,,,,,,256,256,256,256,,256,,256,256,