Skip to content

Commit

Permalink
Add MetalPerformanceShadersGraph Bindings (#14303)
Browse files Browse the repository at this point in the history
I'm very pleased to present full bindings to the MetalPerformanceShadersGraph framework!

I'm happy with how everything turned out with the exception of a few notes and questions below.

I re-implemented Apple's MNIST sample (from https://developer.apple.com/documentation/metalperformanceshadersgraph/training_a_neural_network_using_mps_graph) here:

https://gist.github.com/praeclarum/b8077771fb341a1f9c28240113e00425

It's also added as a unit test.

Fixes #14286

### Notes

* Although the API says it works on macOS 11, it has bugs and crashes with errors even with Apple’s Swift examples. It’s better on macOS 12. iOS 14 and on is fine.

* `MPSGraphSparseStorageType` has terrible names. They match Apple's but I wish they were better.

* I added convenience methods to `MPSNDArray` and `MPSGrapTensorData` and the `Variable` and `Constant` operations to decrease the amount of unsafe code users have to write. I currently do this for 32-bit floats, the most common data type.

Co-authored-by: Alex Soto <[email protected]>
Co-authored-by: Rolf Bjarne Kvinge <[email protected]>
Co-authored-by: Manuel de la Pena <[email protected]>
  • Loading branch information
4 people authored May 10, 2022
1 parent 8cf0231 commit bd4fee0
Show file tree
Hide file tree
Showing 26 changed files with 2,463 additions and 3,215 deletions.
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@
/src/metalkit.cs
/src/MetalPerformanceShaders
/src/metalperformanceshaders.cs
/src/MetalPerformanceShadersGraph @praeclarum
/src/metalperformanceshadersgraph.cs @praeclarum
/src/MobileCoreServices
/src/mobilecoreservices.cs
/src/ModelIO
Expand Down
1 change: 1 addition & 0 deletions src/Foundation/NSObject.mac.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public partial class NSObject : INativeObject
static IntPtr ios = Dlfcn.dlopen (Constants.IOSurfaceLibrary, 1);
static IntPtr ex = Dlfcn.dlopen (Constants.ExternalAccessoryLibrary, 1);
static IntPtr ms = Dlfcn.dlopen (Constants.MetalPerformanceShadersLibrary, 1);
static IntPtr msg = Dlfcn.dlopen (Constants.MetalPerformanceShadersGraphLibrary, 1);
static IntPtr bc = Dlfcn.dlopen (Constants.BusinessChatLibrary, 1);
static IntPtr ad = Dlfcn.dlopen (Constants.AdSupportLibrary, 1);
static IntPtr nl = Dlfcn.dlopen (Constants.NaturalLanguageLibrary, 1);
Expand Down
42 changes: 42 additions & 0 deletions src/MetalPerformanceShaders/MPSNDArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
namespace MetalPerformanceShaders {
public partial class MPSNDArray {

public static MPSNDArray Create (IMTLDevice device, ReadOnlySpan<float> values, params int[] shape)
{
var ushape = new nuint [shape.Length];
for (var i = 0; i < shape.Length; i++) {
ushape [i] = (nuint) shape [i];
}
var desc = MPSNDArrayDescriptor.Create (MPSDataType.Float32, ushape);
var ndarray = new MPSNDArray (device, desc);
ndarray.Write (values);
return ndarray;
}

public void ExportData (IMTLCommandBuffer cmdBuf, IMTLBuffer buffer, MPSDataType sourceDataType, nuint offset)
{
ExportData (cmdBuf, buffer, sourceDataType, offset, IntPtr.Zero);
Expand Down Expand Up @@ -37,6 +49,21 @@ public unsafe void WriteBytes (IntPtr buffer, nint[] strideBytesPerDimension)
WriteBytes (buffer, (IntPtr)p);
}
}
public unsafe void Write (ReadOnlySpan<float> values)
{
if (DataType != MPSDataType.Float32)
throw new InvalidOperationException($"Attempted to write array data of type {DataType} to span of Float32s.");
nuint length = 1;
var ndims = NumberOfDimensions;
for (nuint i = 0; i < ndims; i++) {
length *= GetLength (i);
}
if (length != (nuint) values.Length)
throw new ArgumentException ($"The number of values ({values.Length}) does not match the shape length ({length}).");
fixed (float* p = values) {
WriteBytes ((IntPtr) p, strideBytesPerDimension: IntPtr.Zero);
}
}

public void ReadBytes (IntPtr buffer)
{
Expand All @@ -48,5 +75,20 @@ public unsafe void ReadBytes (IntPtr buffer, nint[] strideBytesPerDimension)
ReadBytes (buffer, (IntPtr)p);
}
}
public unsafe void Read (Span<float> values)
{
if (DataType != MPSDataType.Float32)
throw new InvalidOperationException ($"Attempted to read array data of type {DataType} to span of Float32s.");
nuint length = 1;
var ndims = NumberOfDimensions;
for (nuint i = 0; i < ndims; i++) {
length *= GetLength (i);
}
if (length != (nuint) values.Length)
throw new ArgumentException ($"The number of values ({values.Length}) does not match the shape length ({length}).");
fixed (float* p = values) {
ReadBytes ((IntPtr) p, strideBytesPerDimension: IntPtr.Zero);
}
}
}
}
117 changes: 117 additions & 0 deletions src/MetalPerformanceShadersGraph/MPSGraphEnums.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System;
using System.Runtime.InteropServices;

using Foundation;
using ObjCRuntime;
using Metal;

namespace MetalPerformanceShadersGraph
{
[Flags]
public enum MPSGraphOptions : ulong
{
None = 0,
SynchronizeResults = 1,
Verbose = 2,
Default = SynchronizeResults,
}

[Native]
public enum MPSGraphTensorNamedDataLayout : ulong
{
Nchw = 0,
Nhwc = 1,
Oihw = 2,
Hwio = 3,
Chw = 4,
Hwc = 5,
Hw = 6,
}

[Native]
public enum MPSGraphPaddingStyle : ulong
{
Explicit = 0,
Valid = 1,
Same = 2,
ExplicitOffset = 3,
}

[Native]
public enum MPSGraphPaddingMode : long
{
Constant = 0,
Reflect = 1,
Symmetric = 2,
ClampToEdge = 3,
Zero = 4,
Periodic = 5,
AntiPeriodic = 6,
}

[Native]
public enum MPSGraphReductionMode : ulong
{
Min = 0,
Max = 1,
Sum = 2,
Product = 3,
ArgumentMin = 4,
ArgumentMax = 5,
}

[Native]
public enum MPSGraphResizeMode : ulong
{
Nearest = 0,
Bilinear = 1,
}

[Native]
public enum MPSGraphScatterMode : long
{
Add = 0,
Sub = 1,
Mul = 2,
Div = 3,
Min = 4,
Max = 5,
Set = 6,
}

public enum MPSGraphDeviceType : uint
{
Metal = 0,
}

public enum MPSGraphLossReductionType : ulong
{
Axis = 0,
Sum = 1,
Mean = 2,
}

// For COO, indexTensor0 is x index and indexTensor1 is y index
// For CSC, indexTensor0 and indexTensor1 correspond to rowIndex and colStarts respectively.
// For CSR, indexTensor0 and indexTensor1 correspond to colIndex and rowStarts respectively.
public enum MPSGraphSparseStorageType : ulong
{
Coo = 0,
Csc = 1,
Csr = 2,
}

public enum MPSGraphRandomDistribution : ulong
{
Uniform = 0,
Normal = 1,
TruncatedNormal = 2,
}

public enum MPSGraphRandomNormalSamplingMethod : ulong
{
InvCdf = 0,
BoxMuller = 1,
}

}
60 changes: 60 additions & 0 deletions src/MetalPerformanceShadersGraph/MPSGraphExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#nullable enable

using System;
using System.Buffers;
using System.Runtime.InteropServices;

using Foundation;
using ObjCRuntime;
using Metal;
using MetalPerformanceShaders;

namespace MetalPerformanceShadersGraph
{
public static partial class MPSGraphMemoryOps_Extensions
{
public static unsafe MPSGraphTensor Constant (this MPSGraph graph, float scalar)
{
return graph.Constant ((double) scalar, new [] { 1 }, MPSDataType.Float32);
}

public static unsafe MPSGraphTensor Constant (this MPSGraph graph, ReadOnlySpan<float> values, int[] shape)
{
var length = 1;
for (var i = 0; i < shape.Length; i++)
length *= shape [i];
if (length != values.Length)
throw new ArgumentException ($"The number of values ({values.Length}) does not match the shape length ({length}).");
fixed (float* p = values) {
using var data = NSData.FromBytesNoCopy ((IntPtr) p, (nuint) (values.Length * 4), freeWhenDone: false);
return graph.Constant (data, shape, MPSDataType.Float32);
}
}

public static MPSGraphTensor Variable (this MPSGraph graph, float initialValue, int[] shape, string? name = null)
{
var length = 1;
for (var i = 0; i < shape.Length; i++)
length *= shape [i];
var pool = ArrayPool<float>.Shared;
var a = pool.Rent (length);
Array.Fill (a, initialValue);
var v = Variable (graph, a, shape, name);
pool.Return (a);
return v;
}

public static unsafe MPSGraphTensor Variable (this MPSGraph graph, ReadOnlySpan<float> initialValues, int[] shape, string? name = null)
{
var length = 1;
for (var i = 0; i < shape.Length; i++)
length *= shape [i];
if (length != initialValues.Length)
throw new ArgumentException ($"The number of initial values ({initialValues.Length}) does not match the shape length ({length}).");
fixed (float* p = initialValues) {
using var data = NSData.FromBytesNoCopy ((IntPtr) p, (nuint) (initialValues.Length * 4), freeWhenDone: false);
return graph.Variable (data, shape, MPSDataType.Float32, name);
}
}
}
}
33 changes: 33 additions & 0 deletions src/MetalPerformanceShadersGraph/MPSGraphTensorData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#nullable enable

using System;
using System.Buffers;
using System.Runtime.InteropServices;

using Foundation;
using ObjCRuntime;
using Metal;
using MetalPerformanceShaders;

namespace MetalPerformanceShadersGraph
{
public partial class MPSGraphTensorData
{
public static MPSGraphTensorData Create (IMTLDevice device, ReadOnlySpan<float> values, params int[] shape)
{
var ndarray = MPSNDArray.Create (device, values, shape);
return new MPSGraphTensorData (ndarray);
}

public static MPSGraphTensorData Create (params MPSImage[] imageBatch)
{
return new MPSGraphTensorData (NSArray<MPSImage>.FromNSObjects (imageBatch));
}

public void Read (Span<float> values)
{
using var ndarray = this.MPSNDArray;
ndarray.Read (values);
}
}
}
13 changes: 13 additions & 0 deletions src/frameworks.sources
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,15 @@ METALPERFORMANCESHADERS_SOURCES = \
MetalPerformanceShaders/MPSStateBatch.cs \
MetalPerformanceShaders/MPSStateResourceList.cs \

# MetalPerformanceShadersGraph

METALPERFORMANCESHADERSGRAPH_API_SOURCES = \
MetalPerformanceShadersGraph/MPSGraphEnums.cs \

METALPERFORMANCESHADERSGRAPH_SOURCES = \
MetalPerformanceShadersGraph/MPSGraphExtensions.cs \
MetalPerformanceShadersGraph/MPSGraphTensorData.cs \

# MetricKit

METRICKIT_SOURCES = \
Expand Down Expand Up @@ -2026,6 +2035,7 @@ MACOS_FRAMEWORKS = \
Metal \
MetalKit \
MetalPerformanceShaders \
MetalPerformanceShadersGraph \
MetricKit \
MLCompute \
MobileCoreServices \
Expand Down Expand Up @@ -2135,6 +2145,7 @@ IOS_FRAMEWORKS = \
Metal \
MetalKit \
MetalPerformanceShaders \
MetalPerformanceShadersGraph \
MetricKit \
MLCompute \
MobileCoreServices \
Expand Down Expand Up @@ -2239,6 +2250,7 @@ TVOS_FRAMEWORKS = \
Metal \
MetalKit \
MetalPerformanceShaders \
MetalPerformanceShadersGraph \
MLCompute \
MobileCoreServices \
ModelIO \
Expand Down Expand Up @@ -2321,6 +2333,7 @@ MACCATALYST_FRAMEWORKS = \
Metal \
MetalKit \
MetalPerformanceShaders \
MetalPerformanceShadersGraph \
MetricKit \
MLCompute \
MobileCoreServices \
Expand Down
2 changes: 2 additions & 0 deletions src/generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,8 @@ public NamespaceManager (BindingTouch binding_touch, string customObjCRuntimeNS,
ImplicitNamespaces.Add ("ModelIO");
if (Frameworks.HaveMetal)
ImplicitNamespaces.Add ("Metal");
if (Frameworks.HaveMetalPerformanceShadersGraph)
ImplicitNamespaces.Add ("MetalPerformanceShadersGraph");

if (Frameworks.HaveCoreImage)
ImplicitNamespaces.Add ("CoreImage");
Expand Down
Loading

5 comments on commit bd4fee0

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ [CI Build] Tests failed on VSTS: simulator tests iOS ❌

Tests failed on VSTS: simulator tests iOS.

Test results

32 tests failed, 202 tests passed.

Failed tests

  • link sdk/Mac [dotnet]/Debug [dotnet]: Failed (Test run failed.
    Tests run: 117 Passed: 107 Inconclusive: 0 Failed: 2 Ignored: 8)
  • link sdk/Mac [dotnet]/Release [dotnet]: Failed (Test run failed.
    Tests run: 117 Passed: 107 Inconclusive: 0 Failed: 1 Ignored: 9)
  • link sdk/Mac Catalyst [dotnet]/Debug [dotnet]: Failed (Test run failed.
    Tests run: 129 Passed: 119 Inconclusive: 0 Failed: 2 Ignored: 8)
  • link sdk/Mac Catalyst [dotnet]/Release [dotnet]: Failed (Test run failed.
    Tests run: 129 Passed: 118 Inconclusive: 0 Failed: 2 Ignored: 9)
  • link sdk/Mac Modern/Debug: Failed (Test run failed.
    Tests run: 9 Passed: 8 Inconclusive: 0 Failed: 1 Ignored: 0)
  • link sdk/Mac Modern/Release: Failed (Test run failed.
    Tests run: 9 Passed: 8 Inconclusive: 0 Failed: 1 Ignored: 0)
  • link sdk/iOS Unified 64-bits - simulator/Debug [dotnet]: Failed
  • link sdk/iOS Unified 64-bits - simulator/Release [dotnet]: Failed
  • link sdk/iOS Unified 64-bits - simulator/Release: Failed
  • link sdk/tvOS - simulator/Debug [dotnet]: Failed
  • link sdk/tvOS - simulator/Release [dotnet]: Failed
  • link sdk/tvOS - simulator/Release: Failed
  • trimmode link/Mac [dotnet]/Debug [dotnet]: Failed (Test run failed.
    Tests run: 117 Passed: 107 Inconclusive: 0 Failed: 2 Ignored: 8)
  • trimmode link/Mac [dotnet]/Release [dotnet]: Failed (Test run failed.
    Tests run: 117 Passed: 106 Inconclusive: 0 Failed: 2 Ignored: 9)
  • trimmode link/Mac Catalyst [dotnet]/Debug [dotnet]: Failed (Test run failed.
    Tests run: 129 Passed: 120 Inconclusive: 0 Failed: 1 Ignored: 8)
  • trimmode link/Mac Catalyst [dotnet]/Release [dotnet]: Failed (Test run failed.
    Tests run: 129 Passed: 118 Inconclusive: 0 Failed: 2 Ignored: 9)
  • trimmode link/iOS Unified 64-bits - simulator/Debug [dotnet]: Failed
  • trimmode link/iOS Unified 64-bits - simulator/Release [dotnet]: Failed
  • trimmode link/tvOS - simulator/Debug [dotnet]: Failed
  • trimmode link/tvOS - simulator/Release [dotnet]: Failed
  • link all/Mac Modern/Debug: Failed (Test run failed.
    Tests run: 20 Passed: 18 Inconclusive: 0 Failed: 1 Ignored: 1)
  • link all/Mac Modern/Release: Failed (Test run failed.
    Tests run: 20 Passed: 18 Inconclusive: 0 Failed: 1 Ignored: 1)
  • Xtro/Legacy Xamarin: Failed (Test run failed.)
  • monotouch-test/iOS Unified 64-bits - simulator/Debug: BuildFailure
  • monotouch-test/iOS Unified 64-bits - simulator/Debug (LinkSdk): BuildFailure
  • monotouch-test/iOS Unified 64-bits - simulator/Debug (static registrar): BuildFailure
  • monotouch-test/iOS Unified 64-bits - simulator/Release (all optimizations): BuildFailure
  • monotouch-test/iOS Unified 64-bits - simulator/Debug (all optimizations): BuildFailure
  • introspection/iOS Unified 64-bits - simulator/Debug: Failed
  • introspection/iOS Unified 64-bits - simulator/Debug (iOS 12.4): Failed
  • MTouch tests/NUnit: Failed (Execution failed with exit code 1)
  • Generator tests/NUnit: Failed (Execution failed with exit code 99)

Pipeline on Agent XAMBOT-1098.Monterey
Add MetalPerformanceShadersGraph Bindings (#14303)

Please sign in to comment.