using System.Runtime.CompilerServices; using ILGPU; using ILGPU.Runtime; using PerfectNumbers.Core.Gpu.Accelerators; namespace PerfectNumbers.Core.Gpu; public class GpuKernelPool { [ThreadStatic] private static KernelContainer[]? _pool; public static T InitOnce(ref T? target, Func valueFactory) where T : class { if (target is { } value) { return value; } var newValue = valueFactory(); return Interlocked.CompareExchange(ref target, newValue, null) ?? newValue; } private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; internal static KernelContainer GetOrAddKernels(int acceleratorIndex, AcceleratorStream stream, KernelType kernelType) { var pool = _pool ??= []; if (pool[acceleratorIndex] is not { } kernels) { kernels = new(); } Accelerator accelerator = _accelerators[acceleratorIndex]; if (kernelType.HasFlag(KernelType.OrderKernelScan)) { InitOrderKernelScan(accelerator, kernels); } if (kernelType.HasFlag(KernelType.Pow2ModKernelScan)) { InitPow2ModKernelScan(accelerator, kernels); } if (kernelType.HasFlag(KernelType.IncrementalKernelScan)) { InitIncrementalKernelScan(accelerator, kernels); } if (kernelType.HasFlag(KernelType.IncrementalOrderKernelScan)) { IncrementalOrderKernelScan(accelerator, kernels); } if (kernelType.HasFlag(KernelType.Pow2ModOrderKernelScan)) { InitPow2ModOrderKernelScan(accelerator, kernels); } PreloadStaticTables(accelerator, kernels, stream, kernelType); pool[acceleratorIndex] = kernels; return kernels; static void InitOrderKernelScan(Accelerator accelerator, KernelContainer kernels) { InitOnce(ref kernels.Order, () => { var loaded = accelerator.LoadAutoGroupedStreamKernel, ArrayView>(OrderKernels.OrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate, ArrayView>>(); }); } [MethodImpl(MethodImplOptions.AggressiveOptimization)] static void InitPow2ModKernelScan(Accelerator accelerator, KernelContainer kernels) { InitOnce(ref kernels.Pow2Mod, () => { var loaded = accelerator.LoadAutoGroupedStreamKernel, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>(Pow2ModKernels.Pow2ModKernelScan); var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>>(); }); } static void InitIncrementalKernelScan(Accelerator accelerator, KernelContainer kernels) { InitOnce(ref kernels.Incremental, () => { var loaded = accelerator.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalKernels.IncrementalKernelScan); var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate, ArrayView1D>>(); }); } static void IncrementalOrderKernelScan(Accelerator accelerator, KernelContainer kernels) { InitOnce(ref kernels.IncrementalOrder, () => { var loaded = accelerator.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalKernels.IncrementalOrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate, ArrayView1D>>(); }); } static void InitPow2ModOrderKernelScan(Accelerator accelerator, KernelContainer kernels) { InitOnce(ref kernels.Pow2ModOrder, () => { var loaded = accelerator.LoadAutoGroupedStreamKernel, ArrayView1D>(Pow2ModKernels.Pow2ModOrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); return kernel.CreateLauncherDelegate, ArrayView1D>>(); }); } } internal static void PreloadStaticTables(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream, KernelType kernelType) { if (kernelType.HasFlag(KernelType.SmallCycles)) { EnsureSmallCyclesOnDevice(accelerator, kernels, stream); } if (kernelType.HasFlag(KernelType.SmallPrimes)) { EnsureSmallPrimesOnDevice(accelerator, kernels, stream); } } public static ArrayView1D GetSmallCyclesOnDevice(KernelContainer kernels) => kernels.SmallCycles!; // Ensures the small cycles table is uploaded to the device for the given accelerator. // Returns the ArrayView to pass into kernels (when kernels are extended to accept it). private static void EnsureSmallCyclesOnDevice(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream) { if (kernels.SmallCycles is { }) { return; } var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); MemoryBuffer1D? device; // lock (accelerator) { device = accelerator.Allocate1D(host.Length); } device.View.CopyFromCPU(stream, host); kernels.SmallCycles = device; } public static ResiduePrimeViews GetSmallPrimesOnDevice(KernelContainer kernels) => new( kernels.SmallPrimesLastOne!, kernels.SmallPrimesLastSeven!, kernels.SmallPrimesPow2LastOne!, kernels.SmallPrimesPow2LastSeven! ); private static void EnsureSmallPrimesOnDevice(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream) { if (kernels.SmallPrimesLastOne is { } && kernels.SmallPrimesLastSeven is { } && kernels.SmallPrimesPow2LastOne is { } && kernels.SmallPrimesPow2LastSeven is { }) { return; } if (kernels.SmallPrimesLastOne is { } && kernels.SmallPrimesLastSeven is { } && kernels.SmallPrimesPow2LastOne is { } && kernels.SmallPrimesPow2LastSeven is { }) { return; } var hostLastOne = PrimesGenerator.SmallPrimesLastOne; var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven; var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne; var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven; MemoryBuffer1D? deviceLastOne; MemoryBuffer1D? deviceLastSeven; MemoryBuffer1D? deviceLastOnePow2; MemoryBuffer1D? deviceLastSevenPow2; // lock (accelerator) { deviceLastOne = accelerator.Allocate1D(hostLastOne.Length); deviceLastSeven = accelerator.Allocate1D(hostLastSeven.Length); deviceLastOnePow2 = accelerator.Allocate1D(hostLastOnePow2.Length); deviceLastSevenPow2 = accelerator.Allocate1D(hostLastSevenPow2.Length); } deviceLastOne.View.CopyFromCPU(stream, hostLastOne); deviceLastSeven.View.CopyFromCPU(stream, hostLastSeven); deviceLastOnePow2.View.CopyFromCPU(stream, hostLastOnePow2); deviceLastSevenPow2.View.CopyFromCPU(stream, hostLastSevenPow2); kernels.SmallPrimesLastOne = deviceLastOne; kernels.SmallPrimesLastSeven = deviceLastSeven; kernels.SmallPrimesPow2LastOne = deviceLastOnePow2; kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2; } /// /// Runs a GPU action with an acquired accelerator and stream. /// /// Action to run with (Accelerator, Stream). public static void Run(Action action) { // GpuPrimeWorkLimiter.Acquire(); var acceleratorIndex = AcceleratorPool.Shared.Rent(); var accelerator = _accelerators[acceleratorIndex]; var stream = AcceleratorStreamPool.Rent(acceleratorIndex); action(accelerator, stream); stream.Synchronize(); stream.Dispose(); accelerator.Dispose(); // GpuPrimeWorkLimiter.Release(); } }