Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
033a887
Add func typedefs
adrianlizarraga Jul 9, 2025
fcdb5cf
Merge branch 'main' into adrianl/compile-api-output-stream
adrianlizarraga Jul 9, 2025
03eb5fa
stub apis
adrianlizarraga Jul 15, 2025
3310968
merge main
adrianlizarraga Jul 17, 2025
c3693de
new branch. add 2 streams first
adrianlizarraga Jul 19, 2025
a69d5f9
Move away from using Graph's graph_proto_ member
adrianlizarraga Jul 19, 2025
5743dcd
fix deref assignment
adrianlizarraga Jul 20, 2025
fd87e0c
Clean up
adrianlizarraga Jul 21, 2025
a40f463
Merge branch 'main' into adrianl/compile-api-output-stream
adrianlizarraga Jul 21, 2025
0dadf4d
Use std::filesystem::path in ModelCompilationOptions; fix memleak in …
adrianlizarraga Jul 21, 2025
d94cf44
fix unused variable warning (as error)
adrianlizarraga Jul 21, 2025
5bfbddb
Merge main and fix conflicts
adrianlizarraga Aug 28, 2025
69a4338
Update handler function signature to take in the ExternalDataInfo for…
adrianlizarraga Aug 28, 2025
90ade82
Add test that reuses external initializers from original model
adrianlizarraga Aug 29, 2025
c36afe5
Define new ExternalDataInfo constructor only for non-minimal builds
adrianlizarraga Aug 29, 2025
c07dc11
Merge branch 'main' into adrianl/compile-api-output-stream
adrianlizarraga Aug 29, 2025
4b83a2b
Fix unused variable warning (as error)
adrianlizarraga Aug 29, 2025
91acc8f
another unused variable
adrianlizarraga Aug 29, 2025
6e5629a
Merge branch 'main' into adrianl/compile-api-output-stream
adrianlizarraga Aug 29, 2025
9b092bf
clean up
adrianlizarraga Aug 29, 2025
049b9ad
Start adding csharp api funcs
adrianlizarraga Aug 29, 2025
8e00a06
Remove qnn_factory memleak fix (address in different PR)
adrianlizarraga Aug 29, 2025
11a6c74
Add ExternalInitializerInfo to C++ api
adrianlizarraga Aug 29, 2025
9ca882f
Add compile_to_stream py api
adrianlizarraga Aug 29, 2025
6d522d8
Python bindings and tests
adrianlizarraga Aug 30, 2025
af996bb
C# API for WriteBuffer delegate
adrianlizarraga Aug 31, 2025
9b27b31
c# api handle initializers
adrianlizarraga Aug 31, 2025
9607193
missing documentation in c#
adrianlizarraga Aug 31, 2025
e65710a
Add ExternalInitializerInfo C# class
adrianlizarraga Aug 31, 2025
c16b327
Full C# API for delegate that handles initializers
adrianlizarraga Sep 1, 2025
0b2f0e6
Update comment
adrianlizarraga Sep 2, 2025
83758d1
Merge branch 'main' into adrianl/compile-api-output-stream
adrianlizarraga Sep 2, 2025
c62ed23
Address review comments
adrianlizarraga Sep 2, 2025
a35e7b6
Address review comments
adrianlizarraga Sep 3, 2025
d906855
Remove unused variable
adrianlizarraga Sep 3, 2025
255c2df
Merge branch 'main' into adrianl/compile-api-output-stream
adrianlizarraga Sep 3, 2025
3db3117
Merge main conflicts
adrianlizarraga Sep 3, 2025
c7f98de
Merge main again
adrianlizarraga Sep 3, 2025
9031635
Address review comments for C#
adrianlizarraga Sep 3, 2025
abd0297
Rename functions in C and python
adrianlizarraga Sep 3, 2025
d5012fb
Merge branch 'main' into adrianl/compile-api-output-stream
adrianlizarraga Sep 3, 2025
0e0497a
Address comments
adrianlizarraga Sep 4, 2025
0a61f1f
Merge branch 'main' into adrianl/compile-api-output-stream
adrianlizarraga Sep 4, 2025
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
296 changes: 296 additions & 0 deletions csharp/src/Microsoft.ML.OnnxRuntime/CompileModel.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,287 @@ public void SetFlags(OrtCompileApiFlags flags)
NativeMethods.CompileApi.OrtModelCompilationOptions_SetFlags(handle, (uint)flags));
}

/// <summary>
/// Sets information related to EP context binary file. The Ep uses this information to decide the
/// location and context binary file name when compiling with both the input and output models
/// stored in buffers.
/// </summary>
/// <param name="outputDirectory">Path to the model directory.</param>
/// <param name="modelName">The name of the model.</param>
public void SetEpContextBinaryInformation(string outputDirectory, string modelName)
{
var platformOutputDirectory = NativeOnnxValueHelper.GetPlatformSerializedString(outputDirectory);
var platformModelName = NativeOnnxValueHelper.GetPlatformSerializedString(modelName);
NativeApiStatus.VerifySuccess(
NativeMethods.CompileApi.OrtModelCompilationOptions_SetEpContextBinaryInformation(
handle, platformOutputDirectory, platformModelName));
}

/// <summary>
/// Delegate to write/save a buffer containing ONNX model bytes to a custom destination.
Comment thread
yuslepukhin marked this conversation as resolved.
Outdated
/// </summary>
/// <param name="buffer">The buffer to write out.</param>
public delegate void WriteBufferDelegate(ReadOnlySpan<byte> buffer);
Comment thread
yuslepukhin marked this conversation as resolved.
Outdated

/// <summary>
/// Sets a delegate that is called by ORT to write out the output model's serialized ONNX bytes.
/// The provided delegate may be called repeatedly until the entire output model has been written out.
/// Each call to the delegate is expected to consume/handle the entire input buffer.
/// </summary>
/// <param name="writeBufferDelegate">The delegate called by ORT to write out the model.</param>
public void SetOutputModelWriteBufferDelegate(WriteBufferDelegate writeBufferDelegate)
{
_writeBufferDelegateState?.Dispose();
_writeBufferDelegateState =
new DelegateResources<WriteBufferConnector, NativeMethods.DOrtWriteBufferDelegate>(
new WriteBufferConnector(writeBufferDelegate),
new NativeMethods.DOrtWriteBufferDelegate(WriteBufferConnector.WriteBufferDelegateWrapper));

IntPtr funcPtr = _writeBufferDelegateState.GetFunctionPointerForDelegate();

NativeApiStatus.VerifySuccess(
NativeMethods.CompileApi.OrtModelCompilationOptions_SetOutputModelWriteFunc(
handle,
funcPtr,
_writeBufferDelegateState.GetConnectorHandleAsPointer()));
}

/// <summary>
/// Delegate called by ORT for every initializer when generating the compiled model.
/// The delegate allows the user to determine whether the initializer should be stored within the compiled
/// model or externally in a file. If the delegate chooses to store an initializer externally, the delegate
/// implementation is responsible for writing the initializer data to a file.
/// </summary>
/// <param name="initializerName">The initializer's name.</param>
/// <param name="initializerValue">The readonly OrtValue instance containing the data, type, and
/// shape of the initializer.</param>
/// <param name="optionalExternalInfo">May be null. If the initializer is originally stored externally,
Comment thread
yuslepukhin marked this conversation as resolved.
Outdated
/// this contains the file path, file offset, and data size. Otherwise, this is null.</param>
/// <returns>A new OrtExternalInitializerInfo indicating the new location of the initializer.
/// Returns null if the initializer should be stored within the generated compiled model.</returns>
/// <remarks>The return value may be null.</remarks>
public delegate OrtExternalInitializerInfo HandleInitializerDelegate(
Comment thread
yuslepukhin marked this conversation as resolved.
Outdated
string initializerName,
IReadOnlyOrtValue initializerValue,
IReadOnlyExternalInitializerInfo optionalExternalInfo);

/// <summary>
/// Sets a delegate that is called by ORT for every initializer when generating the compiled model.
/// The delegate allows the user to determine whether the initializer should be stored within the compiled
/// model or externally in a file. If the delegate chooses to store an initializer externally, the delegate
/// implementation is responsible for writing the initializer data to a file.
/// </summary>
/// <param name="handleInitializerDelegate">The delegate called by ORT for every initializer.</param>
public void SetOutputModelHandleInitializerDelegate(HandleInitializerDelegate handleInitializerDelegate)
{
_handleInitializerDelegateState?.Dispose();
_handleInitializerDelegateState =
new DelegateResources<HandleInitializerConnector, NativeMethods.DOrtHandleInitializerDataDelegate>(
new HandleInitializerConnector(handleInitializerDelegate),
new NativeMethods.DOrtHandleInitializerDataDelegate(
HandleInitializerConnector.HandleInitializerDelegateWrapper));

IntPtr funcPtr = _handleInitializerDelegateState.GetFunctionPointerForDelegate();

NativeApiStatus.VerifySuccess(
NativeMethods.CompileApi.OrtModelCompilationOptions_SetOutputModelHandleInitializerFunc(
handle,
funcPtr,
_handleInitializerDelegateState.GetConnectorHandleAsPointer()));
}

#region Delegate helpers
/// <summary>
/// Class to bridge the C# and native worlds for the "write buffer" delegate
/// </summary>
private class WriteBufferConnector
{
private readonly WriteBufferDelegate _csharpDelegate;

internal WriteBufferConnector(WriteBufferDelegate writeBufferDelegate)
{
_csharpDelegate = writeBufferDelegate;
}

public static IntPtr WriteBufferDelegateWrapper(IntPtr /* void* */ state,
IntPtr /* const void* */ buffer,
UIntPtr /* size_t */ bufferNumBytes)
{
try
{

WriteBufferConnector connector = (WriteBufferConnector)GCHandle.FromIntPtr(state).Target;
ReadOnlySpan<byte> bufferSpan;

unsafe
{
// NOTE: A Span<byte> can only view 2GB of data. This is fine because ORT does not write out
// chunks that large. However, if we ever need to, the solution is to just write a loop here
// that repeatedly calls the delegate with smaller chunks of data.
bufferSpan = new ReadOnlySpan<byte>(buffer.ToPointer(), checked((int)bufferNumBytes));
}

connector._csharpDelegate(bufferSpan);
}
catch (Exception ex)
{
var error = $"The C# WriteBuffer delegate threw an exception: {ex.Message}";
IntPtr status = NativeMethods.OrtCreateStatus((uint)ErrorCode.Fail,
NativeOnnxValueHelper.StringToZeroTerminatedUtf8(error));
return status;
}

return IntPtr.Zero;
}
}

/// <summary>
/// Class to bridge the C# and native worlds for the "handle initializer" delegate
/// </summary>
private class HandleInitializerConnector
{
private readonly HandleInitializerDelegate _csharpDelegate;

internal HandleInitializerConnector(HandleInitializerDelegate handleInitializerDelegate)
{
_csharpDelegate = handleInitializerDelegate;
}

public static IntPtr HandleInitializerDelegateWrapper(
IntPtr /* void* */ state,
IntPtr /* const char* */ initializerName,
IntPtr /* const OrtValue* */ initializerValue,
IntPtr /* const OrtExternalInitializerInfo* */ externalInfo,
out IntPtr /* OrtExternalInitializerInfo** */ newExternalInfo)
{
newExternalInfo = IntPtr.Zero;

try
{

HandleInitializerConnector connector = (HandleInitializerConnector)GCHandle.FromIntPtr(state).Target;
string utf8InitializerName = NativeOnnxValueHelper.StringFromNativeUtf8(initializerName);
IReadOnlyOrtValue readOnlyInitializerValue = new OrtValue(initializerValue, owned: false);
IReadOnlyExternalInitializerInfo readOnlyExternalInfo = null;

if (externalInfo != IntPtr.Zero)
{
readOnlyExternalInfo = new OrtExternalInitializerInfo(externalInfo, ownsHandle: false);
}

// Call user's delegate, which may return the new location of the initializer.
OrtExternalInitializerInfo optionalNewExternalInfo = connector._csharpDelegate(
Comment thread
yuslepukhin marked this conversation as resolved.
Outdated
utf8InitializerName, readOnlyInitializerValue, readOnlyExternalInfo);

if (optionalNewExternalInfo != null)
{
// Delegate returned info about a new location for the initializer.
// Can't guarantee that the new external info returned by user's delegate is not referenced
// by other C# code. ORT expects to own the new external info, so create a copy here and
// give it to ORT.
string newFilePath = optionalNewExternalInfo.GetFilePath();
byte[] newFilePathBytes = NativeOnnxValueHelper.GetPlatformSerializedString(newFilePath);

IntPtr status = NativeMethods.OrtCreateExternalInitializerInfo(
newFilePathBytes,
optionalNewExternalInfo.GetFileOffset(),
(UIntPtr)optionalNewExternalInfo.GetByteSize(),
out newExternalInfo);

if (status != IntPtr.Zero)
{
return status;
}
}
else
{
// User's delegate did not return a new location for the initializer. ORT will store initializer
// within the generated compiled model.
newExternalInfo = IntPtr.Zero;
}
}
catch (Exception ex)
{
var error = $"The C# HandleInitializer delegate threw an exception: {ex.Message}";
IntPtr status = NativeMethods.OrtCreateStatus((uint)ErrorCode.Fail,
NativeOnnxValueHelper.StringToZeroTerminatedUtf8(error));
return status;
}

return IntPtr.Zero;
}
}

/// <summary>
/// Disposable class that stores resources for a delegate provided by the user.
/// </summary>
/// <typeparam name="Connector">The type of the connector class (e.g., WriteBufferConnector)</typeparam>
/// <typeparam name="Delegate">The type of the native delegate.</typeparam>
private class DelegateResources<Connector, Delegate> : IDisposable
where Connector : class
where Delegate : class
{
public DelegateResources(Connector connector, Delegate @delegate)
{
_connector = connector;
_delegate = @delegate;
_connectorHandle = GCHandle.Alloc(_connector);
_delegateHandle = GCHandle.Alloc(_delegate);
}

public IntPtr GetFunctionPointerForDelegate()
{
return Marshal.GetFunctionPointerForDelegate(_delegate);
}

public IntPtr GetConnectorHandleAsPointer()
{
return GCHandle.ToIntPtr(_connectorHandle);
}
Comment thread
yuslepukhin marked this conversation as resolved.

#region IDispose implementation
public void Dispose()
{
DisposeImpl();
GC.SuppressFinalize(this);
}

protected virtual void DisposeImpl()
Comment thread
yuslepukhin marked this conversation as resolved.
Outdated
{
if (_disposed)
{
return;
}

if (_connectorHandle.IsAllocated)
{
_connectorHandle.Free();
_connector = null;
}

if (_delegateHandle.IsAllocated)
{
_delegateHandle.Free();
_delegate = null;
}

_disposed = true;
}

~DelegateResources()
{
DisposeImpl();
}
#endregion

private Connector _connector = null;
private Delegate _delegate = null;
private GCHandle _connectorHandle = default;
private GCHandle _delegateHandle = default;
private bool _disposed = false;
}
#endregion

internal IntPtr Handle => handle;


Expand All @@ -146,7 +427,22 @@ protected override bool ReleaseHandle()
{
NativeMethods.CompileApi.OrtReleaseModelCompilationOptions(handle);
handle = IntPtr.Zero;

_writeBufferDelegateState?.Dispose();
Comment thread
yuslepukhin marked this conversation as resolved.
Outdated
_handleInitializerDelegateState?.Dispose();
return true;
}

/// <summary>
/// Stores delegate state for the "write buffer" delegate.
/// </summary>
private DelegateResources<WriteBufferConnector, NativeMethods.DOrtWriteBufferDelegate>
_writeBufferDelegateState = null;

/// <summary>
/// Stores delegate state for the "handle initializer" delegate.
/// </summary>
private DelegateResources<HandleInitializerConnector, NativeMethods.DOrtHandleInitializerDataDelegate>
_handleInitializerDelegateState = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public struct OrtCompileApi
public IntPtr ModelCompilationOptions_SetEpContextEmbedMode;
public IntPtr CompileModel;
public IntPtr ModelCompilationOptions_SetFlags;
public IntPtr ModelCompilationOptions_SetEpContextBinaryInformation;
public IntPtr ModelCompilationOptions_SetOutputModelWriteFunc;
public IntPtr ModelCompilationOptions_SetOutputModelHandleInitializerFunc;
}

internal class NativeMethods
Expand Down Expand Up @@ -101,6 +104,31 @@ public DOrtModelCompilationOptions_SetOutputModelExternalInitializersFile
uint flags);
public DOrtModelCompilationOptions_SetFlags OrtModelCompilationOptions_SetFlags;

[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate IntPtr /* OrtStatus* */ DOrtModelCompilationOptions_SetEpContextBinaryInformation(
IntPtr /* OrtModelCompilationOptions* */ options,
byte[] /* const ORTCHAR_T* */ outputDirectory,
byte[] /* const ORTCHAR_T* */ modelName);
public DOrtModelCompilationOptions_SetEpContextBinaryInformation
OrtModelCompilationOptions_SetEpContextBinaryInformation;

[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate IntPtr /* OrtStatus* */ DOrtModelCompilationOptions_SetOutputModelWriteFunc(
IntPtr /* OrtModelCompilationOptions* */ options,
IntPtr /* DOrtWriteBufferDelegate */ writeFunc,
IntPtr /* void* */ state);
public DOrtModelCompilationOptions_SetOutputModelWriteFunc
OrtModelCompilationOptions_SetOutputModelWriteFunc;

[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate IntPtr /* OrtStatus* */ DOrtModelCompilationOptions_SetOutputModelHandleInitializerFunc(
IntPtr /* OrtModelCompilationOptions* */ options,
IntPtr /* DOrtHandleInitializerDataDelegate */ handleInitializerFunc,
IntPtr /* void* */ state);
public DOrtModelCompilationOptions_SetOutputModelHandleInitializerFunc
OrtModelCompilationOptions_SetOutputModelHandleInitializerFunc;


internal NativeMethods(OnnxRuntime.NativeMethods.DOrtGetCompileApi getCompileApi)
{

Expand Down Expand Up @@ -161,6 +189,21 @@ internal NativeMethods(OnnxRuntime.NativeMethods.DOrtGetCompileApi getCompileApi
_compileApi.ModelCompilationOptions_SetFlags,
typeof(DOrtModelCompilationOptions_SetFlags));

OrtModelCompilationOptions_SetEpContextBinaryInformation =
(DOrtModelCompilationOptions_SetEpContextBinaryInformation)Marshal.GetDelegateForFunctionPointer(
_compileApi.ModelCompilationOptions_SetEpContextBinaryInformation,
typeof(DOrtModelCompilationOptions_SetEpContextBinaryInformation));

OrtModelCompilationOptions_SetOutputModelWriteFunc =
(DOrtModelCompilationOptions_SetOutputModelWriteFunc)Marshal.GetDelegateForFunctionPointer(
_compileApi.ModelCompilationOptions_SetOutputModelWriteFunc,
typeof(DOrtModelCompilationOptions_SetOutputModelWriteFunc));

OrtModelCompilationOptions_SetOutputModelHandleInitializerFunc =
(DOrtModelCompilationOptions_SetOutputModelHandleInitializerFunc)Marshal.GetDelegateForFunctionPointer(
_compileApi.ModelCompilationOptions_SetOutputModelHandleInitializerFunc,
typeof(DOrtModelCompilationOptions_SetOutputModelHandleInitializerFunc));

}
}
}
Loading
Loading