using PerfectNumbers.Core; 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; public Action, int, MontgomeryDivisorData, ArrayView1D, ArrayView1D>? SpecialMax; // 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? SpecialMaxFactors; public MemoryBuffer1D? SpecialMaxCandidates; public MemoryBuffer1D? SpecialMaxResult; 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 void Dispose() => GpuKernelPool.Return(this); } 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( MemoryBuffer1D primes, MemoryBuffer1D squares, int count) { public readonly MemoryBuffer1D Primes = primes; public readonly MemoryBuffer1D Squares = squares; public readonly int Count = count; public ArrayView1D PrimesView => Primes.View; public ArrayView1D SquaresView => Squares.View; } public readonly struct SmallPrimeFactorScratch( MemoryBuffer1D primeSlots, MemoryBuffer1D exponentSlots, MemoryBuffer1D countSlot, MemoryBuffer1D remainingSlot) { public readonly MemoryBuffer1D PrimeSlots = primeSlots; public readonly MemoryBuffer1D ExponentSlots = exponentSlots; public readonly MemoryBuffer1D CountSlot = countSlot; public readonly MemoryBuffer1D RemainingSlot = remainingSlot; public void Clear() { PrimeSlots.MemSetToZero(); ExponentSlots.MemSetToZero(); CountSlot.MemSetToZero(); RemainingSlot.MemSetToZero(); } } public readonly struct SpecialMaxScratch( MemoryBuffer1D factorValues, MemoryBuffer1D candidateValues, MemoryBuffer1D resultSlot) { public readonly MemoryBuffer1D FactorValues = factorValues; public readonly MemoryBuffer1D CandidateValues = candidateValues; public readonly MemoryBuffer1D ResultSlot = resultSlot; public ArrayView1D FactorsView => FactorValues.View; public ArrayView1D CandidatesView => CandidateValues.View; public ArrayView1D ResultView => ResultSlot.View; } public class GpuKernelPool { private static readonly ConcurrentQueue 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. public static KernelContainer GetKernels() => KernelCache.TryDequeue(out var container) ? container : new KernelContainer(); public static void Return(KernelContainer container) => KernelCache.Enqueue(container); internal static void PreloadStaticTables(KernelContainer kernels, Accelerator accelerator) { EnsureSmallCyclesOnDevice(kernels, accelerator); EnsureSmallPrimesOnDevice(kernels, accelerator); EnsureSmallPrimeFactorTables(kernels, accelerator); } // 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(KernelContainer kernels, Accelerator accelerator) { if (kernels.SmallCycles is { } buffer) { return buffer.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(KernelContainer kernels, Accelerator 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); } 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(KernelContainer kernels, Accelerator accelerator) { if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer) { return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length); } 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(KernelContainer kernels, Accelerator accelerator, int slotCount) { 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 SpecialMaxScratch EnsureSpecialMaxScratch(KernelContainer kernels, Accelerator accelerator, int factorCapacity) { MemoryBuffer1D? factorValues = kernels.SpecialMaxFactors; if (factorValues is null || factorValues.Length < factorCapacity) { factorValues?.Dispose(); factorValues = accelerator.Allocate1D(factorCapacity); kernels.SpecialMaxFactors = factorValues; } MemoryBuffer1D? candidateValues = kernels.SpecialMaxCandidates; if (candidateValues is null || candidateValues.Length < factorCapacity) { candidateValues?.Dispose(); candidateValues = accelerator.Allocate1D(factorCapacity); kernels.SpecialMaxCandidates = candidateValues; } MemoryBuffer1D? resultSlot = kernels.SpecialMaxResult; if (resultSlot is null) { resultSlot = accelerator.Allocate1D(1); kernels.SpecialMaxResult = resultSlot; } return new SpecialMaxScratch(factorValues!, candidateValues!, resultSlot!); } public static GpuKernelLease GetKernel(bool useGpuOrder) { GpuPrimeWorkLimiter.Acquire(); var gpu = Rent(); var kernels = GetKernels(); return GpuKernelLease.Rent(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 accelerator = lease.Accelerator; var stream = accelerator.CreateStream(); action(accelerator, stream); stream.Dispose(); lease.Dispose(); } }