using System.Collections.Concurrent; using ILGPU; using ILGPU.Runtime; using static PerfectNumbers.Core.Gpu.GpuContextPool; namespace PerfectNumbers.Core.Gpu; public readonly struct ResidueAutomatonArgs { public readonly ulong Q0M10; public readonly ulong Step10; public readonly ulong Q0M8; public readonly ulong Step8; public readonly ulong Q0M3; public readonly ulong Step3; public readonly ulong Q0M5; public readonly ulong Step5; public ResidueAutomatonArgs(ulong q0m10, ulong step10, ulong q0m8, ulong step8, ulong q0m3, ulong step3, ulong q0m5, ulong step5) { Q0M10 = q0m10; Step10 = step10; Q0M8 = q0m8; Step8 = step8; Q0M3 = q0m3; Step3 = step3; Q0M5 = q0m5; Step5 = step5; } } public sealed class KernelContainer { // Serializes first-time initialization of kernels/buffers per accelerator. public Action, ArrayView>? Order; public Action, ArrayView1D>? Incremental; public Action, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>? Pow2Mod; public Action, ArrayView1D>? IncrementalOrder; public Action, ArrayView1D>? Pow2ModOrder; public Action, ArrayView1D, int, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>? SmallPrimeFactor; // Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length. public MemoryBuffer1D? SmallCycles; public MemoryBuffer1D? SmallPrimeFactorsPrimes; public MemoryBuffer1D? SmallPrimeFactorsSquares; public MemoryBuffer1D? SmallPrimeFactorPrimeSlots; public MemoryBuffer1D? SmallPrimeFactorExponentSlots; public MemoryBuffer1D? SmallPrimeFactorCountSlot; public MemoryBuffer1D? SmallPrimeFactorRemainingSlot; public MemoryBuffer1D? SmallPrimesLastOne; public MemoryBuffer1D? SmallPrimesPow2LastOne; public MemoryBuffer1D? SmallPrimesLastSeven; public MemoryBuffer1D? SmallPrimesPow2LastSeven; public static T InitOnce(ref T? slot, Func factory) where T : class { var current = Volatile.Read(ref slot); if (current is not null) { return current; } current = Volatile.Read(ref slot); if (current is null) { current = factory(); Volatile.Write(ref slot, current); } return current; } } public readonly struct ResiduePrimeViews( ArrayView1D lastOne, ArrayView1D lastSeven, ArrayView1D lastOnePow2, ArrayView1D lastSevenPow2) { public readonly ArrayView1D LastOne = lastOne; public readonly ArrayView1D LastSeven = lastSeven; public readonly ArrayView1D LastOnePow2 = lastOnePow2; public readonly ArrayView1D LastSevenPow2 = lastSevenPow2; } public readonly struct SmallPrimeFactorTables { public readonly MemoryBuffer1D Primes; public readonly MemoryBuffer1D Squares; public readonly int Count; public SmallPrimeFactorTables( MemoryBuffer1D primes, MemoryBuffer1D squares, int count) { Primes = primes; Squares = squares; Count = count; } public ArrayView1D PrimesView => Primes.View; public ArrayView1D SquaresView => Squares.View; } public readonly struct SmallPrimeFactorScratch { public readonly MemoryBuffer1D PrimeSlots; public readonly MemoryBuffer1D ExponentSlots; public readonly MemoryBuffer1D CountSlot; public readonly MemoryBuffer1D RemainingSlot; public SmallPrimeFactorScratch( MemoryBuffer1D primeSlots, MemoryBuffer1D exponentSlots, MemoryBuffer1D countSlot, MemoryBuffer1D remainingSlot) { PrimeSlots = primeSlots; ExponentSlots = exponentSlots; CountSlot = countSlot; RemainingSlot = remainingSlot; } public void Clear() { PrimeSlots.MemSetToZero(); ExponentSlots.MemSetToZero(); CountSlot.MemSetToZero(); RemainingSlot.MemSetToZero(); } } public class GpuKernelPool { private static readonly ConcurrentDictionary KernelCache = new(); // TODO: Replace this concurrent map with a simple accelerator-indexed lookup once kernel launchers are prewarmed during startup so we can drop the thread-safe wrapper entirely. private static KernelContainer GetKernels(Accelerator accelerator) { return KernelCache.GetOrAdd(accelerator, _ => new KernelContainer()); } // 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). public static ArrayView1D EnsureSmallCyclesOnDevice(Accelerator accelerator) { var kernels = GetKernels(accelerator); if (kernels.SmallCycles is { } buffer) { return buffer.View; } // Ensure single upload per accelerator even if multiple threads race here. lock (kernels) // TODO: Remove this lock by pre-uploading the immutable small-cycle snapshot during initialization; once no mutation happens at runtime, the pool must expose a simple reference without synchronization. { if (kernels.SmallCycles is { } existing) { return existing.View; } var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // TODO: Preload this device buffer during startup and keep it immutable so we can delete the lock above in favor of the preloaded snapshot. var device = accelerator.Allocate1D(host.Length); device.View.CopyFromCPU(host); kernels.SmallCycles = device; return device.View; } } public static ResiduePrimeViews EnsureSmallPrimesOnDevice(Accelerator accelerator) { var kernels = GetKernels(accelerator); if (kernels.SmallPrimesLastOne is { } lastOne && kernels.SmallPrimesLastSeven is { } lastSeven && kernels.SmallPrimesPow2LastOne is { } lastOnePow2 && kernels.SmallPrimesPow2LastSeven is { } lastSevenPow2) { return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View); } lock (kernels) // TODO: Inline these small-prime uploads into startup initialization alongside the small-cycle snapshot so we can drop runtime locking and keep the GPU pool free of synchronization. { if (kernels.SmallPrimesLastOne is { } existingLastOne && kernels.SmallPrimesLastSeven is { } existingLastSeven && kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 && kernels.SmallPrimesPow2LastSeven is { } existingLastSevenPow2) { return new ResiduePrimeViews(existingLastOne.View, existingLastSeven.View, existingLastOnePow2.View, existingLastSevenPow2.View); } var hostLastOne = PrimesGenerator.SmallPrimesLastOne; var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven; var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne; var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven; var deviceLastOne = accelerator.Allocate1D(hostLastOne.Length); deviceLastOne.View.CopyFromCPU(hostLastOne); var deviceLastSeven = accelerator.Allocate1D(hostLastSeven.Length); deviceLastSeven.View.CopyFromCPU(hostLastSeven); var deviceLastOnePow2 = accelerator.Allocate1D(hostLastOnePow2.Length); deviceLastOnePow2.View.CopyFromCPU(hostLastOnePow2); var deviceLastSevenPow2 = accelerator.Allocate1D(hostLastSevenPow2.Length); deviceLastSevenPow2.View.CopyFromCPU(hostLastSevenPow2); kernels.SmallPrimesLastOne = deviceLastOne; kernels.SmallPrimesLastSeven = deviceLastSeven; kernels.SmallPrimesPow2LastOne = deviceLastOnePow2; kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2; return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View); } } public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(Accelerator accelerator) { var kernels = GetKernels(accelerator); if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer) { return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length); } lock (kernels) { if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares) { return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length); } var hostPrimes = PrimesGenerator.SmallPrimes; var hostSquares = PrimesGenerator.SmallPrimesPow2; var devicePrimes = accelerator.Allocate1D(hostPrimes.Length); devicePrimes.View.CopyFromCPU(hostPrimes); var deviceSquares = accelerator.Allocate1D(hostSquares.Length); deviceSquares.View.CopyFromCPU(hostSquares); kernels.SmallPrimeFactorsPrimes = devicePrimes; kernels.SmallPrimeFactorsSquares = deviceSquares; return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length); } } public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(Accelerator accelerator, int slotCount) { var kernels = GetKernels(accelerator); lock (kernels) { MemoryBuffer1D? primeSlots = kernels.SmallPrimeFactorPrimeSlots; if (primeSlots is null || primeSlots.Length < slotCount) { primeSlots?.Dispose(); primeSlots = accelerator.Allocate1D(slotCount); kernels.SmallPrimeFactorPrimeSlots = primeSlots; } MemoryBuffer1D? exponentSlots = kernels.SmallPrimeFactorExponentSlots; if (exponentSlots is null || exponentSlots.Length < slotCount) { exponentSlots?.Dispose(); exponentSlots = accelerator.Allocate1D(slotCount); kernels.SmallPrimeFactorExponentSlots = exponentSlots; } MemoryBuffer1D? countSlot = kernels.SmallPrimeFactorCountSlot; if (countSlot is null) { countSlot = accelerator.Allocate1D(1); kernels.SmallPrimeFactorCountSlot = countSlot; } MemoryBuffer1D? remainingSlot = kernels.SmallPrimeFactorRemainingSlot; if (remainingSlot is null) { remainingSlot = accelerator.Allocate1D(1); kernels.SmallPrimeFactorRemainingSlot = remainingSlot; } return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!); } } public static GpuKernelLease GetKernel(bool useGpuOrder) { var limiter = GpuPrimeWorkLimiter.Acquire(); var gpu = RentPreferred(preferCpu: !useGpuOrder); var accelerator = gpu.Accelerator; var kernels = GetKernels(accelerator); return GpuKernelLease.Rent(limiter, gpu, kernels); } /// /// Runs a GPU action with an acquired accelerator and stream. /// /// Action to run with (Accelerator, Stream). public static void Run(Action action) { var lease = GetKernel(useGpuOrder: true); var execution = lease.EnterExecutionScope(); var accelerator = lease.Accelerator; var stream = accelerator.CreateStream(); action(accelerator, stream); stream.Dispose(); execution.Dispose(); lease.Dispose(); } }