using System.Collections.Concurrent; using ILGPU; using ILGPU.Runtime; namespace PerfectNumbers.Core.Gpu; public sealed class GpuContextLease { private GpuContextPool.PooledContext? _ctx; internal GpuContextLease(GpuContextPool.PooledContext ctx) { _ctx = ctx; } public Context Context => _ctx!.Context; public Accelerator Accelerator => _ctx!.Accelerator; public void Dispose() => GpuContextPool.Return(_ctx!); } public static class GpuContextPool { // The pool intentionally skips pre-loading ProcessEightBitWindows kernels so accelerator initialization // stays stable; GpuKernelPool JIT-compiles them on first use alongside the small-cycle uploads. // Default device preference for generic GPU kernels (prime scans, NTT, etc.) internal sealed class PooledContext { public readonly Context Context; public readonly Accelerator Accelerator; public PooledContext() { Context = SharedGpuContext.Context; Accelerator = SharedGpuContext.Accelerator; // NOTE: Avoid loading/compiling any kernel here to prevent implicit // CL stream/queue creation during accelerator construction. // Some OpenCL drivers are fragile when a queue is created immediately // after device init. Real kernels will be JIT-loaded at first use. } public void Dispose() { // Ensure all cached GPU buffers for this accelerator are released. // Release PrimeTester GPU state for this accelerator (kernel/device primes) // Clear any per-accelerator cached resources to avoid releasing // after the accelerator is destroyed. // These resources are shared between GPU leases // Accelerator.Dispose(); // Context.Dispose(); } } private static readonly ConcurrentQueue GpuPool = new(); private static int WarmedGpuContextCount; // Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu. public static GpuContextLease Rent() { if (GpuPool.TryDequeue(out var gpu)) { return new GpuContextLease(gpu); } // Create a new accelerator when the pool does not have one available. return new GpuContextLease(new PooledContext()); } public static void WarmUpPool(int threadCount) { if (threadCount <= WarmedGpuContextCount) { return; } int toCreate = threadCount - WarmedGpuContextCount; Console.WriteLine($"Warming up GPU context pool to {toCreate} contexts..."); var contexts = new PooledContext[toCreate]; var pool = GpuPool; PooledContext context; for (int i = 0; i < toCreate; i++) { context = new PooledContext(); contexts[i] = context; pool.Enqueue(context); } WarmedGpuContextCount = threadCount; } public static void DisposeAll() { while (GpuPool.TryDequeue(out var gpu)) { gpu.Dispose(); } WarmedGpuContextCount = 0; } internal static void Return(PooledContext ctx) { // ctx.Accelerator.Synchronize(); GpuPool.Enqueue(ctx); } }