Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vsts-dotnet-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ jobs:
_targetFramework: net8.0
innerLoop: true
pool:
vmImage: macOS-15
vmImage: macOS-15-arm64
helixQueue: osx.15.arm64.open

- template: /build/ci/job-template.yml
Expand Down
4 changes: 3 additions & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetArchitecture)' == 'arm64' Or '$(TargetArchitecture)' == 'arm'">
<!-- SymSgdNative is built on arm/arm64 (with its CBLAS shim compiled in), so keep it
here so it is copied next to the managed assemblies. There is no separate MklImports
on arm/arm64, so it stays removed like the other MKL-based natives. -->
<NativeAssemblyReference Remove="MklImports"/>
<NativeAssemblyReference Remove="CpuMathNative"/>
<NativeAssemblyReference Remove="FastTreeNative"/>
<NativeAssemblyReference Remove="SymSgdNative"/>
<NativeAssemblyReference Remove="MklProxyNative"/>
<NativeAssemblyReference Remove="libiomp5md"/>
</ItemGroup>
Expand Down
4 changes: 3 additions & 1 deletion build/vsts-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ variables:
value: 1es-windows-2022
- name: MacImage
value: macOS-15
- name: MacArm64Image
value: macOS-15-arm64
- ${{ if and(notin(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) }}:
- name: enableSourceIndex
value: true
Expand Down Expand Up @@ -158,7 +160,7 @@ extends:
################################################################################
pool:
name: Azure Pipelines
vmImage: $(MacImage)
vmImage: $(MacArm64Image)
os: macOS
templateContext:
outputs:
Expand Down
8 changes: 2 additions & 6 deletions docs/building/unix-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,11 @@ macOS 10.13 (High Sierra) or higher is needed to build dotnet/machinelearning. W

On macOS a few components are needed which are not provided by a default developer setup:
* cmake 3.10.3
* libomp 7
* libomp
* gettext
* All the requirements necessary to run .NET Core 3.1 applications. To view macOS prerequisites click [here](https://docs.microsoft.com/en-us/dotnet/core/install/macos?tabs=netcore31#dependencies).

One way of obtaining CMake and other required libraries is via [Homebrew](https://brew.sh):
```sh
$ brew update && brew install cmake https://raw.githubusercontent.com/dotnet/machinelearning/main/build/libomp.rb gettext && brew link gettext --force && brew link libomp --force
$ brew update && brew install cmake libomp gettext && brew link gettext --force && brew link libomp --force
```

Please note that newer versions of Homebrew [don't allow installing directly from a URL](https://github.com/Homebrew/brew/issues/8791). If you run into this issue, you may need to download libomp.rb first and install it with the local file instead.

Also, libomp version 7.0.0 doesn't have a cask for Big Sur. You can work around this by downloading the libomp.rb file and then calling `brew install libomp.rb --build-from-source --formula`.
3 changes: 1 addition & 2 deletions docs/project-docs/components-and-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ML.NET redistributes Intel MKL as Microsoft.ML.MKL.Redist in which is a minimize
| `Microsoft.ML.FastTree` | `FastTreeRankingTrainer` | Optional native acceleration | Stable | Native library used on x86/x64; managed fallback |
| `Microsoft.ML.ImageAnalytics` | `MLImage` (image exchange type) | `libSkiaSharp` | Stable | Wrapper over SkiaSharp / Google Skia; supported where dependency is supported |
| `Microsoft.ML.LightGBM` | `LightGbm\*Trainer` | `LightGBM` | Stable | Wrapper over LightGBM; supported where dependency is supported |
| `Microsoft.ML.MKL.Components` | `SymbolicSgdLogisticRegressionBinaryTrainer` | Intel MKL | Stable | Only works where Intel MKL works |
| `Microsoft.ML.MKL.Components` | `SymbolicSgdLogisticRegressionBinaryTrainer` | Intel MKL (x86/x64); OpenMP runtime | Stable | Uses a built-in CBLAS implementation on ARM |
| `Microsoft.ML.MKL.Redist` | Internal native Intel MKL | `libomp` | Stable | Not for direct reference; win-x86/x64 only |
| `Microsoft.ML.OneDal` | Internal native Intel OneDal | Intel OneDAL | Preview | Not for direct reference; x64 only |
| `Microsoft.ML.OnnxConverter` | Adds ONNX export support | `Microsoft.ML.OnnxRuntime` | Stable | Wrapper over ONNX Runtime; supports "bring your own" runtime |
Expand Down Expand Up @@ -266,4 +266,3 @@ graph TD
class AutoML,CodeGen,FastTree,LightGBM,Recommender,TimeSeries,TorchSharp,ImageAnalytics,DnnFeaturizerAlexNet,DnnFeaturizerResNet18,DnnFeaturizerResNet50,DnnFeaturizerResNet101,DnnFeaturizerModelRedist,Vision,OnnxConverter,OnnxTransformer,TensorFlow,MKLComponents,Ensemble,EntryPoints,Experimental,FairLearn,Parquet,DataAnalysis,GenAICore,GenAILLaMA,GenAIMistral,GenAIPhi,Tokenizers,TokenizersGpt2,TokenizersR50k,TokenizersP50k,TokenizersO200k,TokenizersCl100k,SampleUtils algorithm
class CpuMath,MKLRedist,OneDal bundled
```

4 changes: 3 additions & 1 deletion docs/project-docs/platform-limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ While ML.NET is cross-platform, there are some limitations for specific platform
| **Windows** | Yes | Yes |
| **Linux** | Yes | Yes |
| **macOS** | Yes | Yes |
| **ARM64** / **Apple M1** | Yes, with **limitations**.</br></br>The following are *not supported*:<ul><li>Symbolic SGD</li><li>TensorFlow</li><li>OLS</li><li>TimeSeries SSA</li><li>TimeSeries SrCNN</li><li>ONNX*</li><li>Light GBM*</li></ul>\**You can add support by compiling (no pre-compiled binaries provided).* | Yes, with **limitations**.</br></br>The following are *not supported*: <ul><li>Symbolic SGD</li><li>TensorFlow</li><li>OLS</li><li>TimeSeries SSA</li><li>TimeSeries SrCNN</li><li>ONNX</li></ul>|
| **ARM64** / **Apple M1** | Yes, with **limitations**.</br></br>The following are *not supported*:<ul><li>TensorFlow</li><li>OLS</li><li>TimeSeries SSA</li><li>TimeSeries SrCNN</li><li>ONNX*</li><li>Light GBM*</li></ul>\**You can add support by compiling (no pre-compiled binaries provided).* | Yes, with **limitations**.</br></br>The following are *not supported*: <ul><li>TensorFlow</li><li>OLS</li><li>TimeSeries SSA</li><li>TimeSeries SrCNN</li><li>ONNX</li></ul>|
| **Blazor WASM** | Yes, with **limitations**.</br></br>The following are *not supported*:<ul><li>Symbolic SGD</li><li>TensorFlow</li><li>OLS</li><li>TimeSeries SSA</li><li>TimeSeries SrCNN</li><li>ONNX</li><li>Light GBM</li><li>LDA</li><li>Matrix Factorization</li></ul> *Note: You must currently set the <code>EnableMLUnsupportedPlatformTargetCheck</code> flag to <code>false</code> to use ML.NET in Blazor.* | Yes, with **limitations**.</br></br>The following are *not supported*:<ul><li>Symbolic SGD</li><li>TensorFlow</li><li>OLS</li><li>TimeSeries SSA</li><li>TimeSeries SrCNN</li><li>ONNX</li><li>Light GBM</li><li>LDA</li><li>Matrix Factorization</li> |

> Symbolic SGD on macOS requires the Homebrew `libomp` package.
>
> Note: All the limitations listed above will throw a <code>DLL not found</code> exception.

If you are blocked by any of these limitations or would like to see different behavior when hitting them, please let us know by [filing an issue](https://github.com/dotnet/machinelearning/issues/new?assignees=&labels=&template=suggest-a-feature.md&title=).
25 changes: 19 additions & 6 deletions eng/helix.proj
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@

<HelixPreCommands Condition="$(HelixTargetQueues.ToLowerInvariant().Contains('osx'))">$(HelixPreCommands);export LD_LIBRARY_PATH=/opt/homebrew/opt/mono-libgdiplus/lib;ls /usr/lib;ls $HELIX_WORKITEM_ROOT;export KMP_DUPLICATE_LIB_OK=TRUE;otool -L $HELIX_WORKITEM_ROOT/runtimes/osx-x64/native/lib_lightgbm.dylib</HelixPreCommands>

<!-- macOS x64 only: add DYLD paths so the local libomp/libiomp5 in the work item is discoverable -->
<HelixPreCommands Condition="$(HelixTargetQueues.ToLowerInvariant().Contains('osx')) and '$(BuildArchitecture)'=='x64'">$(HelixPreCommands);export DYLD_LIBRARY_PATH=$HELIX_WORKITEM_ROOT:$DYLD_LIBRARY_PATH;export DYLD_FALLBACK_LIBRARY_PATH=$HELIX_WORKITEM_ROOT:$DYLD_FALLBACK_LIBRARY_PATH</HelixPreCommands>
<!-- Add DYLD paths so the local libomp/libiomp5 in the work item is discoverable. -->
<HelixPreCommands Condition="$(HelixTargetQueues.ToLowerInvariant().Contains('osx'))">$(HelixPreCommands);export DYLD_LIBRARY_PATH=$HELIX_WORKITEM_ROOT:$DYLD_LIBRARY_PATH;export DYLD_FALLBACK_LIBRARY_PATH=$HELIX_WORKITEM_ROOT:$DYLD_FALLBACK_LIBRARY_PATH</HelixPreCommands>

<HelixPreCommands Condition="$(HelixTargetQueues.ToLowerInvariant().Contains('armarch'))">$(HelixPreCommands);sudo apt update;sudo apt-get install libomp-dev libomp5 -y</HelixPreCommands>

Expand All @@ -121,10 +121,23 @@

<RunTestScript Condition="'$(OS)' != 'Windows_NT'">runTests.sh</RunTestScript>
<RunTestScript Condition="'$(OS)' == 'Windows_NT'">.\runTests.cmd</RunTestScript>
</PropertyGroup>

<Exec Condition="$(HelixTargetQueues.ToLowerInvariant().Contains('osx'))"
Command="brew --prefix libomp"
ConsoleToMSBuild="true"
StandardOutputImportance="Low">
<Output TaskParameter="ConsoleOutput" PropertyName="MacLibOmpPrefix" />
</Exec>

<MacFilesToCopy>/usr/local/opt/libomp/lib/libomp.dylib;</MacFilesToCopy>
<PropertyGroup Condition="$(HelixTargetQueues.ToLowerInvariant().Contains('osx'))">
<MacLibOmpPath>$(MacLibOmpPrefix)/lib/libomp.dylib</MacLibOmpPath>
<MacFilesToCopy>$(MacLibOmpPath);</MacFilesToCopy>
</PropertyGroup>

<Error Condition="$(HelixTargetQueues.ToLowerInvariant().Contains('osx')) And !Exists('$(MacLibOmpPath)')"
Text="Unable to find the Homebrew libomp runtime at $(MacLibOmpPath)." />

<!-- Copy OpenMP runtime(s) to the publish folders for OSX -->
<Copy Condition="$(HelixTargetQueues.ToLowerInvariant().Contains('osx'))"
SourceFiles = "$(MacFilesToCopy)"
Expand All @@ -136,12 +149,12 @@

<!-- Fix up install names where we know about libomp.dylib usage -->
<Exec Condition="Exists('$(BUILD_SOURCESDIRECTORY)\artifacts\bin\%(ProjectsWithTargetFramework.Filename)\$(BuildConfig)\%(ProjectsWithTargetFramework.TargetFrameworks)$(PublishFolder)\libSymSgdNative.dylib') AND $(HelixTargetQueues.ToLowerInvariant().Contains('osx'))"
Command="install_name_tool -change &quot;/usr/local/opt/libomp/lib/libomp.dylib&quot; &quot;@loader_path/libomp.dylib&quot; $(BUILD_SOURCESDIRECTORY)/artifacts/bin/%(ProjectsWithTargetFramework.Filename)/$(BuildConfig)/%(ProjectsWithTargetFramework.TargetFrameworks)$(PublishFolder)/libSymSgdNative.dylib" />
Command="install_name_tool -change &quot;$(MacLibOmpPath)&quot; &quot;@loader_path/libomp.dylib&quot; $(BUILD_SOURCESDIRECTORY)/artifacts/bin/%(ProjectsWithTargetFramework.Filename)/$(BuildConfig)/%(ProjectsWithTargetFramework.TargetFrameworks)$(PublishFolder)/libSymSgdNative.dylib" />

<Exec Condition="Exists('$(BUILD_SOURCESDIRECTORY)\artifacts\bin\%(ProjectsWithTargetFramework.Filename)\$(BuildConfig)\%(ProjectsWithTargetFramework.TargetFrameworks)$(PublishFolder)\runtimes\osx-x64\native\lib_lightgbm.dylib') AND $(HelixTargetQueues.ToLowerInvariant().Contains('osx'))"
<Exec Condition="'$(BuildArchitecture)' == 'x64' AND Exists('$(BUILD_SOURCESDIRECTORY)\artifacts\bin\%(ProjectsWithTargetFramework.Filename)\$(BuildConfig)\%(ProjectsWithTargetFramework.TargetFrameworks)$(PublishFolder)\runtimes\osx-x64\native\lib_lightgbm.dylib') AND $(HelixTargetQueues.ToLowerInvariant().Contains('osx'))"
Command="install_name_tool -change &quot;/usr/local/opt/libomp/lib/libomp.dylib&quot; &quot;@rpath/libomp.dylib&quot; $(BUILD_SOURCESDIRECTORY)/artifacts/bin/%(ProjectsWithTargetFramework.Filename)/$(BuildConfig)/%(ProjectsWithTargetFramework.TargetFrameworks)$(PublishFolder)/runtimes/osx-x64/native/lib_lightgbm.dylib" />

<Exec Condition="Exists('$(BUILD_SOURCESDIRECTORY)\artifacts\bin\%(ProjectsWithTargetFramework.Filename)\$(BuildConfig)\%(ProjectsWithTargetFramework.TargetFrameworks)$(PublishFolder)\runtimes\osx-x64\native\libonnxruntime.dylib') AND $(HelixTargetQueues.ToLowerInvariant().Contains('osx'))"
<Exec Condition="'$(BuildArchitecture)' == 'x64' AND Exists('$(BUILD_SOURCESDIRECTORY)\artifacts\bin\%(ProjectsWithTargetFramework.Filename)\$(BuildConfig)\%(ProjectsWithTargetFramework.TargetFrameworks)$(PublishFolder)\runtimes\osx-x64\native\libonnxruntime.dylib') AND $(HelixTargetQueues.ToLowerInvariant().Contains('osx'))"
Command="install_name_tool -change &quot;/usr/local/opt/libomp/lib/libomp.dylib&quot; &quot;@loader_path/libomp.dylib&quot; $(BUILD_SOURCESDIRECTORY)/artifacts/bin/%(ProjectsWithTargetFramework.Filename)/$(BuildConfig)/%(ProjectsWithTargetFramework.TargetFrameworks)$(PublishFolder)/runtimes/osx-x64/native/libonnxruntime.dylib" />

<!-- macOS x64 ONLY: if libiomp5.dylib isn't present in the publish folder, duplicate libomp.dylib as libiomp5.dylib -->
Expand Down
15 changes: 12 additions & 3 deletions src/Microsoft.ML.Mkl.Components/SymSgdClassificationTrainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,16 @@ private void CheckLabel(RoleMappedData examples, out int weightSetCount)
private static unsafe class Native
{
//To triger the loading of MKL library since SymSGD native library depends on it.
static Native() => ErrorMessage(0);
//On ARM there is no MKL: SymSgdNative bundles the small CBLAS shim it needs and no
//libMklImports is shipped, so skip this call (it would fail to load MklImports).
static Native()
{
if (RuntimeInformation.ProcessArchitecture != Architecture.Arm64 &&
RuntimeInformation.ProcessArchitecture != Architecture.Arm)
{
ErrorMessage(0);
}
}

internal const string NativePath = "SymSgdNative";
internal const string MklPath = "MklImports";
Expand All @@ -834,8 +843,8 @@ private static unsafe class Native

[DllImport(NativePath), SuppressUnmanagedCodeSecurity]
private static extern void LearnAll(int totalNumInstances, int* instSizes, int** instIndices,
float** instValues, float* labels, bool tuneLR, ref float lr, float l2Const, float piw, float* weightVector, ref float bias,
int numFeatres, int numPasses, int numThreads, bool tuneNumLocIter, ref int numLocIter, float tolerance, bool needShuffle, bool shouldInitialize,
float** instValues, float* labels, [MarshalAs(UnmanagedType.I1)] bool tuneLR, ref float lr, float l2Const, float piw, float* weightVector, ref float bias,
int numFeatres, int numPasses, int numThreads, [MarshalAs(UnmanagedType.I1)] bool tuneNumLocIter, ref int numLocIter, float tolerance, [MarshalAs(UnmanagedType.I1)] bool needShuffle, [MarshalAs(UnmanagedType.I1)] bool shouldInitialize,
State* state, ChannelCallBack info);

/// <summary>
Expand Down
8 changes: 5 additions & 3 deletions src/Native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,11 @@ if(NOT ${ARCHITECTURE} MATCHES "arm.*")
add_subdirectory(CpuMathNative)
add_subdirectory(FastTreeNative)
add_subdirectory(MklProxyNative)
# TODO: once we fix the 4 intel MKL methods, SymSgdNative will need to go back in.
add_subdirectory(SymSgdNative)
endif()
endif()

# On ARM, SymSgdNative compiles the small MklImportsArm CBLAS shim directly
# (see SymSgdNative/CMakeLists.txt), so we do not build a separate libMklImports.
add_subdirectory(SymSgdNative)

if(${ARCHITECTURE} MATCHES "[xX].*64")
add_subdirectory(OneDalNative)
Expand Down
118 changes: 118 additions & 0 deletions src/Native/MklImportsArm/MklImportsArm.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// ARM replacement for Intel MKL (libMklImports.so).
//
// This provides a small, self-contained libMklImports for arm/arm64 that
// covers exactly the symbols SymSGD needs, with no external BLAS dependency.
// That is important because the cross-compilation sysroots used in CI do not
// ship OpenBLAS (or any system BLAS), so linking against one is not an option.
//
// SymSGD uses only four CBLAS routines:
// * cblas_sdot / cblas_saxpy - dense single-precision dot and AXPY,
// * cblas_sdoti / cblas_saxpyi - their sparse counterparts (MKL extensions).
// All four are implemented below as plain C loops. With -O3 the compiler
// autovectorizes the dense paths to NEON, matching hand-written BLAS closely.
//
// MKL DFTI (FFT) functions are stubbed — they are referenced by the managed
// MKL Components initializer but not used by SymSGD. The stubs return error
// codes so any actual FFT call fails cleanly rather than crashing.

// The native build is compiled with -fvisibility=hidden, so every symbol that
// must be visible to SymSgdNative (the CBLAS routines) or to the managed
// P/Invoke layer (DftiErrorMessage) has to be exported explicitly.
#if defined(_WIN32)
#define MKLIMPORTS_EXPORT __declspec(dllexport)
#else
#define MKLIMPORTS_EXPORT __attribute__((visibility("default")))
#endif

// --- Dense BLAS (CBLAS, level 1) ---

MKLIMPORTS_EXPORT float cblas_sdot(const int n, const float *x, const int incx,
const float *y, const int incy)
{
float result = 0.0f;
if (incx == 1 && incy == 1)
{
for (int i = 0; i < n; i++)
result += x[i] * y[i];
}
else
{
int ix = incx < 0 ? (1 - n) * incx : 0;
int iy = incy < 0 ? (1 - n) * incy : 0;
for (int i = 0; i < n; i++, ix += incx, iy += incy)
result += x[ix] * y[iy];
}
return result;
}

MKLIMPORTS_EXPORT void cblas_saxpy(const int n, const float a, const float *x, const int incx,
float *y, const int incy)
{
if (a == 0.0f)
return;
if (incx == 1 && incy == 1)
{
for (int i = 0; i < n; i++)
y[i] += a * x[i];
}
else
{
int ix = incx < 0 ? (1 - n) * incx : 0;
int iy = incy < 0 ? (1 - n) * incy : 0;
for (int i = 0; i < n; i++, ix += incx, iy += incy)
y[iy] += a * x[ix];
}
}

// --- Sparse BLAS (MKL extensions, not in standard BLAS) ---

MKLIMPORTS_EXPORT void cblas_saxpyi(const int nz, const float a,
const float *x, const int *indx, float *y)
{
for (int i = 0; i < nz; i++)
y[indx[i]] += a * x[i];
}

MKLIMPORTS_EXPORT float cblas_sdoti(const int nz, const float *x,
const int *indx, const float *y)
{
float result = 0.0f;
for (int i = 0; i < nz; i++)
result += x[i] * y[indx[i]];
return result;
}

// --- DFTI (FFT) stubs ---

MKLIMPORTS_EXPORT const char* DftiErrorMessage(long status)
{
return "DFTI not available (arm64 MKL shim build)";
}

MKLIMPORTS_EXPORT long DftiCreateDescriptor(void **h, int precision, int domain, int dim, ...)
{
*h = (void*)0;
return -1;
}

MKLIMPORTS_EXPORT long DftiSetValue(void *h, int param, ...)
{
return -1;
}

MKLIMPORTS_EXPORT long DftiCommitDescriptor(void *h) { return -1; }
MKLIMPORTS_EXPORT long DftiComputeForward(void *h, ...) { return -1; }
MKLIMPORTS_EXPORT long DftiComputeBackward(void *h, ...) { return -1; }
MKLIMPORTS_EXPORT long DftiFreeDescriptor(void **h)
{
// Match MKL's contract: clear the caller's handle after freeing so callers
// that rely on the descriptor being nulled out (e.g. the managed
// FreeDescriptor(ref IntPtr) P/Invoke) behave correctly.
if (h != (void*)0)
*h = (void*)0;
return 0;
}
Loading