From 5c36e7651102309e327e90ba2fb1ee17ed543299 Mon Sep 17 00:00:00 2001 From: Marcel Koester Date: Tue, 22 Aug 2023 16:02:31 +0200 Subject: [PATCH] Added sample to demonstrate Optimization extensions. (#1043) --- .../AlgorithmsOptimization.csproj | 17 ++ Samples/AlgorithmsOptimization/Program.cs | 241 ++++++++++++++++++ Samples/ILGPU.Samples.sln | 7 + 3 files changed, 265 insertions(+) create mode 100644 Samples/AlgorithmsOptimization/AlgorithmsOptimization.csproj create mode 100644 Samples/AlgorithmsOptimization/Program.cs diff --git a/Samples/AlgorithmsOptimization/AlgorithmsOptimization.csproj b/Samples/AlgorithmsOptimization/AlgorithmsOptimization.csproj new file mode 100644 index 000000000..53656165d --- /dev/null +++ b/Samples/AlgorithmsOptimization/AlgorithmsOptimization.csproj @@ -0,0 +1,17 @@ + + + $(LibrarySamplesTargetFrameworks) + Exe + 8.0 + + + + true + AllEnabledByDefault + + + + + + + diff --git a/Samples/AlgorithmsOptimization/Program.cs b/Samples/AlgorithmsOptimization/Program.cs new file mode 100644 index 000000000..f4aa96a73 --- /dev/null +++ b/Samples/AlgorithmsOptimization/Program.cs @@ -0,0 +1,241 @@ +// --------------------------------------------------------------------------------------- +// ILGPU Samples +// Copyright (c) 2023 ILGPU Project +// www.ilgpu.net +// +// File: Program.cs +// +// This file is part of ILGPU and is distributed under the University of Illinois Open +// Source License. See LICENSE.txt for details. +// --------------------------------------------------------------------------------------- + +using ILGPU; +#if NET7_0_OR_GREATER +using ILGPU.Algorithms.Optimization; +using ILGPU.Algorithms.Optimization.Optimizers; +using ILGPU.Algorithms.Random; +using ILGPU.Algorithms.Vectors; +#endif +using ILGPU.Runtime; +using System; + +namespace AlgorithmsOptimization +{ +#if NET7_0_OR_GREATER + + /// + /// Represents a distance function to a uniformly defined n-D point. + /// + public readonly struct DistanceObjective : IOptimizationFunction + { + private readonly float uniformDistanceValue; + + public DistanceObjective(float uniformValue) + { + uniformDistanceValue = uniformValue; + } + + /// + /// Computes the distance to our uniform target vector. + /// + public float Evaluate(LongIndex1D index, Index1D dimension, SingleVectorView positionView) + { + float result = 0; + for (Index1D i = 0; i < dimension; ++i) + { + var vec = positionView[i]; + var dist = vec - new Float32x2(uniformDistanceValue, uniformDistanceValue); + result += dist.X * dist.X + dist.Y * dist.Y; + } + return result / dimension; + } + + /// + /// Minimize our objective. + /// + public bool CurrentIsBetter(float current, float proposed) => current < proposed; + } + + /// + /// The Himmbelblau function from the Wikipedia optimization test functions page: + /// https://en.wikipedia.org/wiki/Test_functions_for_optimization + /// + public readonly struct HimmelblauObjective : IOptimizationFunction + { + public float Evaluate(LongIndex1D index, Index1D dimension, SingleVectorView positionView) + { + var firstVector = positionView[0]; + + float first = (firstVector.X * firstVector.X + firstVector.X - 11); + float second = (firstVector.X + firstVector.Y * firstVector.Y - 7); + return first * first + second * second; + } + + /// + /// Minimize our objective. + /// + public bool CurrentIsBetter(float current, float proposed) => current < proposed; + } + + class Program + { + /// + /// Optimizes our distance function. + /// + static void OptimizeDistance( + Random random, + AcceleratorStream stream, + OptimizationEngine optimizationEngine) + { + // Setup lower and upper bounds for our problems + var lowerBounds = new float[] {-10.0f, -10.0f}; + var upperBounds = new float[] {10.0f, 10.0f}; + + // Transfer bounds to the optimization engine + optimizationEngine.LoadBounds(stream, lowerBounds, upperBounds); + + // Load specific PSO parameters (the default ones in this case) + optimizationEngine.LoadParameters(stream, PSO.DefaultFloatParameters); + + // Setup our vectorized objective function + var objective = new DistanceObjective(5.0f); + + // Create a specialized optimizer taking our objective into account + using var optimizer = + optimizationEngine.CreateOptimizer(stream, random, objective); + + // Begin the optimization process by passing a (potentially) known best position + // and an initial results value (here, float.MaxValue indicating that the currently + // known result is extremely far away from the intended solution). + var bestPosition = new float[] {0.0f, 0.0f}; + var run = optimizer.BeginOptimization(stream, bestPosition, + bestResult: float.MaxValue); + + // If you just want to perform a full optimization run without explicit control over + // all intermediate steps use optimizer.Optimize, as shown in the OptimizeHimmelblau + // method below. + + // Perform 128 steps or use optimizer.Optimize and pass the max number of steps + for (int i = 0; i < 128; ++i) + run.Step(); + + // Finish the optimization run in CPU land. Please note that this call does *not* + // synchronize the optimizer with the GPU, and thus the results may not be valid + // at this point. + var result = run.FinishToCPUAsync(); + + // Use stream.Synchronize() (as shown below) to make sure all results are accessible + // from CPU land + stream.Synchronize(); + + Console.WriteLine("Distance objective: " + result.Result); + Console.WriteLine("Best distance position: " + + string.Join(", ", result.ResultVector.ToArray())); + + // In order to (re-)use the result on the GPU use run.Finish() which gives + // you direct access to the result views pointing to the right locations in + // GPU memory containing the results. As before, the result is associated with + // the given stream and may be invalid when accessed from another stream. + } + + /// + /// Optimizes the Himmelblau function. + /// + static void OptimizeHimmelblau( + Random random, + AcceleratorStream stream, + OptimizationEngine optimizationEngine) + { + // Setup lower and upper bounds for our problems + var lowerBounds = new float[] {-5.0f, -5.0f}; + var upperBounds = new float[] {5.0f, 5.0f}; + + // Transfer bounds to the optimization engine + optimizationEngine.LoadBounds(stream, lowerBounds, upperBounds); + + // Load specific PSO parameters (the default ones in this case) + optimizationEngine.LoadParameters(stream, PSO.DefaultFloatParameters); + + // Setup our vectorized objective function + var objective = new HimmelblauObjective(); + + // Create a specialized optimizer taking our objective into account + using var optimizer = + optimizationEngine.CreateOptimizer(stream, random, objective); + + // Begin the optimization process by passing a (potentially) known best position + // and an initial results value (here, float.MaxValue indicating that the currently + // known result is extremely far away from the intended solution). + var bestPosition = new float[] {0.0f, 0.0f}; + + var result = optimizer.OptimizeToCPUAsync( + stream, + bestPosition, + bestResult: float.MaxValue, + 1024); + + // Use stream.Synchronize() (as shown below) to make sure all results are accessible + // from CPU land + stream.Synchronize(); + + Console.WriteLine("Himmelblau objective: " + result.Result); + Console.WriteLine("Best Himmelblau position: " + + string.Join(", ", result.ResultVector.ToArray())); + + // In order to (re-)use the result on the GPU use result.Optimize(....) which + // you direct access to the result views pointing to the right locations in + // GPU memory containing the results. As before, the result is associated with + // the given stream and may be invalid when accessed from another stream. + } + + static void Main() + { + // Create default context and enable algorithms library + using var context = + Context.Create(builder => builder.Default().EnableAlgorithms()); + + // Create a new RNG on the CPU side + var random = new Random(); + + // For each available device... + foreach (var device in context) + { + // Create the associated accelerator + using var accelerator = device.CreateAccelerator(context); + Console.WriteLine($"Performing operations on {accelerator}"); + + // Create a new processing stream and create a new instance of the parallelized + // particle swarm optimizer with 10240 particles and support for 2D problems. + // A single instance of the optimization engine can be used with different objective + // functions to reuse the allocated buffers associated with this instance. + using var stream = accelerator.CreateStream(); + + // Use a vectorized version to process 2 floats at once, while operating on float + // types and evaluating our objective function yielding floats, as well. RNG-wise + // this sample uses the XorShift64Star RNG. + using var pso = new PSO( + accelerator, + maxNumParticles: 10240, + dimension: 2); + + // Optimize our distance objective + OptimizeDistance(random, stream, pso); + + // Optimize the Himmelblau function + OptimizeHimmelblau(random, stream, pso); + } + } + } + +#else + + class Program + { + static void Main() + { + Console.WriteLine("Cannot use optimization API on frameworks prior to .Net7.0"); + } + } + +#endif +} diff --git a/Samples/ILGPU.Samples.sln b/Samples/ILGPU.Samples.sln index 827759bba..e4e992dca 100644 --- a/Samples/ILGPU.Samples.sln +++ b/Samples/ILGPU.Samples.sln @@ -129,6 +129,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StaticAbstractInterfaceMemb EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlgorithmsSparseMatrix", "AlgorithmsSparseMatrix\AlgorithmsSparseMatrix.csproj", "{FBC7F8FA-8EB1-44EA-969E-B3DD365627ED}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlgorithmsOptimization", "AlgorithmsOptimization\AlgorithmsOptimization.csproj", "{70B69CE3-24A9-463C-B14C-E2934988BBEE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -363,6 +365,10 @@ Global {FBC7F8FA-8EB1-44EA-969E-B3DD365627ED}.Debug|Any CPU.Build.0 = Debug|Any CPU {FBC7F8FA-8EB1-44EA-969E-B3DD365627ED}.Release|Any CPU.ActiveCfg = Release|Any CPU {FBC7F8FA-8EB1-44EA-969E-B3DD365627ED}.Release|Any CPU.Build.0 = Release|Any CPU + {70B69CE3-24A9-463C-B14C-E2934988BBEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70B69CE3-24A9-463C-B14C-E2934988BBEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70B69CE3-24A9-463C-B14C-E2934988BBEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70B69CE3-24A9-463C-B14C-E2934988BBEE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -428,6 +434,7 @@ Global {2EF99A5B-9AAE-44A8-BB41-923DF66A7EAB} = {C1D99632-ED4A-4B08-A14D-4C8DB375934F} {28FD07DE-7B7D-46C3-9EE1-5D50C0E4F126} = {C1D99632-ED4A-4B08-A14D-4C8DB375934F} {FBC7F8FA-8EB1-44EA-969E-B3DD365627ED} = {25BA2234-5778-40BC-9386-9CE87AB87D1F} + {70B69CE3-24A9-463C-B14C-E2934988BBEE} = {25BA2234-5778-40BC-9386-9CE87AB87D1F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {30E502BD-3826-417F-888F-1CE19CF5C6DA}