Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.Net: Bug: Application crashes when using the ONNX connector and OnnxRuntimeGenAI v0.5.0 package #9628

Closed
f2bo opened this issue Nov 9, 2024 · 6 comments · Fixed by #9639 or #9644
Closed
Assignees
Labels
bug Something isn't working .NET Issue or Pull requests regarding .NET code

Comments

@f2bo
Copy link

f2bo commented Nov 9, 2024

Describe the bug
It seems that the latest release of the OnnxRuntimeGenAI (v0.5.0) introduces additional checks to prevent resource leaks (see microsoft/onnxruntime-genai#799). As a result, an application using the ONNX connector crashes when it shuts down if it references this version of the ONNXGenAI runtime.

To Reproduce

Add a reference to the Microsoft.ML.OnnxRuntimeGenAI.DirectML Version="0.5.0" package and run the following code:

// using OgaHandle ogaHandle = new OgaHandle();   <<=== UNCOMMENT FOR ONNX RUNTIME LIBRARY SHUTDOWN

IKernelBuilder builder = Kernel.CreateBuilder();
builder.Services
    .AddOnnxRuntimeGenAIChatCompletion(
            modelId: "phi3.0",
            modelPath: "...PATH TO MODEL...");

IKernelBuilder kernelBuilder = builder.Services.AddKernel();

Kernel kernel = builder.Build();

IChatCompletionService chat = kernel.Services.GetRequiredService<IChatCompletionService>();
ChatHistory history = new();
history.AddUserMessage("Hi");

await foreach (var content in chat.GetStreamingChatMessageContentsAsync(history))
{
    Console.Write(content);
}

Console.WriteLine("\nDone!");

// (chat as IDisposable)?.Dispose();            <<=== UNCOMMENT TO PREVENT RESOURCE LEAKS

Running the code above produces the following output and then crashes:

Hello! How can I assist you today??
Done!
OGA Error: Shutdown must be called before process exit, please check the documentation for the proper API to call to ensure clean shutdown.

I found a workaround based on the changes found in this PR. It seems that declaring (and disposing) an OgaHandle will ensure a proper shutdown. I couldn't find much information about it except for the comment "Responsible for cleaning up the library during shutdown" here.

However, simply declaring an OgaHandle is still insufficient to ensure a clean shutdown. The ONNX connector must also be disposed before the application exits. Otherwise, it will still crash with the following output:

OGA Error: 1 instances of struct Generators::Model were leaked.
OGA Error: 1 instances of struct Generators::Tokenizer were leaked.
    Please see the documentation for the API being used to ensure proper cleanup.

Platform

  • OS: Windows
  • IDE: Visual Studio
  • Language: C#
  • Source: NuGet package version 1.28.0-alpha (ONNX connector)
@f2bo f2bo added the bug Something isn't working label Nov 9, 2024
@markwallace-microsoft markwallace-microsoft added .NET Issue or Pull requests regarding .NET code triage labels Nov 9, 2024
@github-actions github-actions bot changed the title Bug: Application crashes when using the ONNX connector and OnnxRuntimeGenAI v0.5.0 package .Net: Bug: Application crashes when using the ONNX connector and OnnxRuntimeGenAI v0.5.0 package Nov 9, 2024
github-merge-queue bot pushed a commit that referenced this issue Nov 11, 2024
### Motivation and Context

- Add missing integration tests 
- Resolves #9628
@github-project-automation github-project-automation bot moved this from Sprint: In Review to Sprint: Done in Semantic Kernel Nov 11, 2024
@f2bo
Copy link
Author

f2bo commented Nov 11, 2024

Hi @RogerBarreto.

I don't know if I'm missing something but looking at the PR that closed this issue, I don't see anything that addresses the problem I reported above. Moreover, I pulled the latest changes from the repository and tested the code again and it still crashes when it shuts down.

@RogerBarreto RogerBarreto reopened this Nov 11, 2024
@RogerBarreto
Copy link
Member

@f2bo , you are right,

To avoid having the problem shown in the 0.5.0 the example the caller now needs to handle the OlgaHandle class and dispose the completion service.

I'm giving here the final code how it should be to avoid the error.

using var ogaHandle = new OgaHandle();

IKernelBuilder builder = Kernel.CreateBuilder();
builder.Services
            .AddOnnxRuntimeGenAIChatCompletion(
                    modelId: "phi3.0",
                    modelPath: "... path ...");

IKernelBuilder kernelBuilder = builder.Services.AddKernel();

Kernel kernel = builder.Build();

var chat = (kernel.Services.GetRequiredService<IChatCompletionService>() as OnnxRuntimeGenAIChatCompletionService);
ChatHistory history = [];
history.AddUserMessage("Hi");

await foreach (var content in chat.GetStreamingChatMessageContentsAsync(history))
{
Console.Write(content);
}

Console.WriteLine("\nDone!");

chat.Dispose();

@RogerBarreto
Copy link
Member

@f2bo , the OgaHandle as added as part of the service instance, but due to this new behavior on the library as you noticed the service needs to be disposed regardless before the application finishes.

For scenarios like this I would rather than creating a kernel to get the service, create the service directly with a using:

i.e:

using var chat = new OnnxRuntimeGenAIChatCompletionService(
    modelId: "phi3.0",
    modelPath: "... path ...");

await foreach (var content in chat.GetStreamingChatMessageContentsAsync(history))
{
    Console.Write(content);
}

Console.WriteLine("\nDone!");

@f2bo
Copy link
Author

f2bo commented Nov 11, 2024

@RogerBarreto,

the OgaHandle as added as part of the service instance

I'm not sure I follow. Currently, whether I instantiate a new service directly or retrieve it from the DI container, I still need to create an OgaHandle in my code. Isn't that the case?

My concern is that this requires special handling for the ONNX connector and is somewhat brittle. I was hoping that it could be somehow encapsulated behind the code to AddOnnxRuntimeGenAIChatCompletion and that callers wouldn't need to be aware of this requirement.

While I was troubleshooting, I experimented by adding the following to the OnnxRuntimeGenAIChatCompletionService class to ensure that an OgaHandle is created and then disposed when the applications shuts down.

public sealed class OnnxRuntimeGenAIChatCompletionService : IChatCompletionService, IDisposable
{
    ...
    private readonly static OgaHandle s_ogaHandle = new();

    static OnnxRuntimeGenAIChatCompletionService()
        => AppDomain.CurrentDomain.ProcessExit += (sender, e) => s_ogaHandle.Dispose();
  ...
}

The other remaining issue is to ensure that the ONNX connector is disposed before the process ends. Where to do this will depend on how it was created. When using CreateApplicationBuilder to build the DI container, you can using IHost host = builder.Build();

However, when using Kernel kernel = builder.Build(), you need to execute (kernel.Services as IDisposable).Dispose() before shutting down and I was surprised to see that Kernel itself is not disposable. Shouldn't it be?

@RogerBarret0
Copy link
Contributor

RogerBarret0 commented Nov 11, 2024

After some investigation I changed the internal implementation of the service, so no more IDisposable instances will be lurking without the context of Running the inference, this resolved the problem with Oga (messages after closing the application), as well as allowed the service to run without being an IDisposable.

@f2bo
Copy link
Author

f2bo commented Nov 11, 2024

I've left a comment in the PR with some concerns.

github-merge-queue bot pushed a commit that referenced this issue Dec 2, 2024
### Motivation and Context

- The Latest 0.5.0 package also requires the caller to handle the
resources with the `OgaHandler` instance, when a service is instantiated
this resource needs to be also present and exposed together with the
service. Otherwise a message will be sent to the console and the
application will crash before finishing.

- Resolves #9628

---------

Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working .NET Issue or Pull requests regarding .NET code
Projects
Archived in project
4 participants