Skip to content

Commit

Permalink
Removed unnecessary instances and simplified LogProviders + RuntimeOp…
Browse files Browse the repository at this point in the history
…tions (#304)

* Removed unnecessary instances and simplified LogProviders + RuntimeOptions

* Changed logger provider to return IDisposable
  • Loading branch information
sandrohanea authored Dec 21, 2024
1 parent b456f6e commit db4a246
Show file tree
Hide file tree
Showing 17 changed files with 131 additions and 146 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:

- name: Upload trx files
uses: actions/upload-artifact@v4
if: success() || failure() # run this step even if previous step failed
with:
name: test-results-macos
path: ./**/*.trx
Expand Down Expand Up @@ -96,6 +97,7 @@ jobs:

- name: Upload trx files
uses: actions/upload-artifact@v4
if: success() || failure() # run this step even if previous step failed
with:
name: test-results-windows
path: ./**/*.trx
Expand Down Expand Up @@ -141,6 +143,7 @@ jobs:

- name: Upload trx files
uses: actions/upload-artifact@v4
if: success() || failure() # run this step even if previous step failed
with:
name: test-results-linux
path: ./**/*.trx
Expand Down
8 changes: 4 additions & 4 deletions Whisper.net/LibraryLoader/CudaHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal static class CudaHelper
{
public static bool IsCudaAvailable()
{
LogProvider.Log(WhisperLogLevel.Debug, "Checking for CUDA availability.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Checking for CUDA availability.");
INativeCuda? nativeCuda = null;
var cudaDevices = 0;
try
Expand All @@ -22,7 +22,7 @@ public static bool IsCudaAvailable()

if (!NativeLibrary.TryLoad(libName, out var library))
{
LogProvider.Log(WhisperLogLevel.Debug, "Cudart library couldn't be loaded.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Cudart library couldn't be loaded.");
return false;
}
nativeCuda = new NativeLibraryCuda(library);
Expand All @@ -37,11 +37,11 @@ public static bool IsCudaAvailable()
}
catch
{
LogProvider.Log(WhisperLogLevel.Debug, "Cudart library couldn't be loaded.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Cudart library couldn't be loaded.");
return false;
}
#endif
LogProvider.Log(WhisperLogLevel.Debug, $"NUmber of CUDA devices found: {cudaDevices}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"NUmber of CUDA devices found: {cudaDevices}");
return cudaDevices > 0;
}
finally
Expand Down
46 changes: 23 additions & 23 deletions Whisper.net/LibraryLoader/NativeLibraryLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@ public static class NativeLibraryLoader
internal static LoadResult LoadNativeLibrary()
{
#if IOS || MACCATALYST || TVOS
LogProvider.Log(WhisperLogLevel.Debug, "Using LibraryImportInternalWhisper for whisper librar for ios.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Using LibraryImportInternalWhisper for whisper librar for ios.");
return LoadResult.Success(new LibraryImportInternalWhisper());
#elif ANDROID
LogProvider.Log(WhisperLogLevel.Debug, "Using LibraryImportLibWhisper for whisper librar for Android.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Using LibraryImportLibWhisper for whisper librar for Android.");
return LoadResult.Success(new LibraryImportLibWhisper());
#else
// If the user has handled loading the library themselves, we don't need to do anything.
if (RuntimeOptions.Instance.BypassLoading
if (RuntimeOptions.LoadedLibrary.HasValue
|| RuntimeInformation.OSArchitecture.ToString().Equals("wasm", StringComparison.OrdinalIgnoreCase))
{
#if NET8_0_OR_GREATER
LogProvider.Log(WhisperLogLevel.Debug, "Using LibraryImportLibWhisper for whisper library with bypassed loading.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Using LibraryImportLibWhisper for whisper library with bypassed loading.");
return LoadResult.Success(new LibraryImportLibWhisper());
#else
LogProvider.Log(WhisperLogLevel.Debug, "Using DllImportsNativeLibWhisper for whisper library with bypassed loading.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Using DllImportsNativeLibWhisper for whisper library with bypassed loading.");
return LoadResult.Success(new DllImportsNativeLibWhisper());
#endif
}
Expand Down Expand Up @@ -87,30 +87,30 @@ _ when RuntimeInformation.IsOSPlatform(OSPlatform.OSX) => "macos",

var whisperPath = GetLibraryPath(platform, "whisper", runtimePath);

LogProvider.Log(WhisperLogLevel.Debug, $"Trying to load ggml library from {ggmlPath}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Trying to load ggml library from {ggmlPath}");
if (!libraryLoader.TryOpenLibrary(ggmlPath, out var ggmlLibraryHandle))
{
lastError = libraryLoader.GetLastError();
LogProvider.Log(WhisperLogLevel.Debug, $"Failed to load ggml library from {ggmlPath}. Error: {lastError}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Failed to load ggml library from {ggmlPath}. Error: {lastError}");
continue;
}

LogProvider.Log(WhisperLogLevel.Debug, $"Trying to load whisper library from {whisperPath}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Trying to load whisper library from {whisperPath}");
// Ggml was loaded, for this runtimePath, we need to load whisper as well
if (!libraryLoader.TryOpenLibrary(whisperPath, out var whisperHandle))
{
lastError = libraryLoader.GetLastError();
LogProvider.Log(WhisperLogLevel.Debug, $"Failed to load whisper library from {whisperPath}. Error: {lastError}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Failed to load whisper library from {whisperPath}. Error: {lastError}");
continue;
}

LogProvider.Log(WhisperLogLevel.Debug, $"Successfully loaded whisper library from {whisperPath}");
RuntimeOptions.Instance.SetLoadedLibrary(runtimeLibrary);
WhisperLogger.Log(WhisperLogLevel.Debug, $"Successfully loaded whisper library from {whisperPath}");
RuntimeOptions.LoadedLibrary = runtimeLibrary;
#if NETSTANDARD
LogProvider.Log(WhisperLogLevel.Debug, $"Using DllImportsNativeWhisper for whisper library");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Using DllImportsNativeWhisper for whisper library");
var nativeWhisper = new DllImportsNativeWhisper();
#else
LogProvider.Log(WhisperLogLevel.Debug, $"Using NativeLibraryWhisper for whisper library");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Using NativeLibraryWhisper for whisper library");
var nativeWhisper = new NativeLibraryWhisper(whisperHandle, ggmlLibraryHandle);
#endif

Expand Down Expand Up @@ -143,15 +143,15 @@ private static string GetLibraryPath(string platform, string libraryName, string

private static bool IsRuntimeSupported(RuntimeLibrary runtime, string platform, string architecture, List<RuntimeLibrary> runtimeLibraries)
{
LogProvider.Log(WhisperLogLevel.Debug, $"Checking if runtime {runtime} is supported on the platform: {platform}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Checking if runtime {runtime} is supported on the platform: {platform}");
#if !NETSTANDARD
// If AVX is not supported, we can't use CPU runtime on Windows and linux (we should use noavx runtime instead).
if (runtime == RuntimeLibrary.Cpu
&& (platform == "win" || platform == "linux")
&& (architecture == "x86" || architecture == "x64")
&& (!Avx.IsSupported || !Avx2.IsSupported || !Fma.IsSupported))
{
LogProvider.Log(WhisperLogLevel.Debug, $"No AVX, AVX2 or Fma support is identified on this host. AVX: {Avx.IsSupported} AVX2: {Avx2.IsSupported} FMA: {Fma.IsSupported}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"No AVX, AVX2 or Fma support is identified on this host. AVX: {Avx.IsSupported} AVX2: {Avx2.IsSupported} FMA: {Fma.IsSupported}");
// If noavx runtime is not available, we should throw an exception, because we can't use CPU runtime without AVX support.
if (!runtimeLibraries.Contains(RuntimeLibrary.CpuNoAvx))
{
Expand All @@ -166,19 +166,19 @@ private static bool IsRuntimeSupported(RuntimeLibrary runtime, string platform,
{
var cudaIndex = runtimeLibraries.IndexOf(RuntimeLibrary.Cuda);

if (cudaIndex == RuntimeOptions.Instance.RuntimeLibraryOrder.Count - 1)
if (cudaIndex == RuntimeOptions.RuntimeLibraryOrder.Count - 1)
{
// We still can use Cuda as a fallback to the CPU if it's the last runtime in the list.
// This scenario can be used to not install 2 runtimes (CPU and Cuda) on the same host,
// + override the default RuntimeLibraryOrder to have only [ Cuda ].
// This way, the user can use Cuda if it's available, otherwise, the CPU runtime will be used.
// However, the cudart library should be available in the system.
LogProvider.Log(WhisperLogLevel.Debug, "Cuda runtime is not available, but it's the last runtime in the list. " +
WhisperLogger.Log(WhisperLogLevel.Debug, "Cuda runtime is not available, but it's the last runtime in the list. " +
"It will be used as a fallback to the CPU runtime.");
return true;
}

LogProvider.Log(WhisperLogLevel.Debug, "Cuda driver is not available or no cuda device is identified.");
WhisperLogger.Log(WhisperLogLevel.Debug, "Cuda driver is not available or no cuda device is identified.");
return false;
}

Expand All @@ -193,14 +193,14 @@ private static bool IsRuntimeSupported(RuntimeLibrary runtime, string platform,
// NetFramework and Mono will crash if we try to get the directory of an empty string.
var assemblySearchPaths = new[]
{
GetSafeDirectoryName(RuntimeOptions.Instance.LibraryPath),
GetSafeDirectoryName(RuntimeOptions.LibraryPath),
AppDomain.CurrentDomain.RelativeSearchPath,
AppDomain.CurrentDomain.BaseDirectory,
GetSafeDirectoryName(assemblyLocation),
GetSafeDirectoryName(environmentAppStartLocation),
}.Where(it => !string.IsNullOrEmpty(it)).Distinct();

foreach (var library in RuntimeOptions.Instance.RuntimeLibraryOrder)
foreach (var library in RuntimeOptions.RuntimeLibraryOrder)
{
foreach (var assemblySearchPath in assemblySearchPaths)
{
Expand All @@ -217,15 +217,15 @@ private static bool IsRuntimeSupported(RuntimeLibrary runtime, string platform,
RuntimeLibrary.OpenVino => Path.Combine(runtimesPath, "openvino", $"{platform}-{architecture}"),
_ => throw new InvalidOperationException("Unknown runtime library")
};
LogProvider.Log(WhisperLogLevel.Debug, $"Searching for runtime directory {library} in {runtimePath}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Searching for runtime directory {library} in {runtimePath}");

if (Directory.Exists(runtimePath))
{
yield return (runtimePath, library);
}
else
{
LogProvider.Log(WhisperLogLevel.Debug, $"Runtime directory for {library} not found in {runtimePath}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Runtime directory for {library} not found in {runtimePath}");
}
}

Expand All @@ -245,7 +245,7 @@ private static bool IsRuntimeSupported(RuntimeLibrary runtime, string platform,
}
catch (Exception ex)
{
LogProvider.Log(WhisperLogLevel.Debug, $"Failed to get directory name from path: {path}. Error: {ex.Message}");
WhisperLogger.Log(WhisperLogLevel.Debug, $"Failed to get directory name from path: {path}. Error: {ex.Message}");
return null;
}
#endif
Expand Down
70 changes: 17 additions & 53 deletions Whisper.net/LibraryLoader/RuntimeOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,36 @@

namespace Whisper.net.LibraryLoader;

public class RuntimeOptions
/// <summary>
/// Provides options for configuring the Whisper runtime.
/// </summary>
/// <remarks>
/// Setting values in this class will affect the behavior of the Whisper runtime only if they are done before any <seealso cref="WhisperFactory"/> is created.
/// </remarks>
public static class RuntimeOptions
{
private static readonly List<RuntimeLibrary> defaultRuntimeOrder = [RuntimeLibrary.Cuda, RuntimeLibrary.Vulkan, RuntimeLibrary.CoreML, RuntimeLibrary.OpenVino, RuntimeLibrary.Cpu, RuntimeLibrary.CpuNoAvx];
internal bool BypassLoading { get; private set; }
internal string? LibraryPath { get; private set; }

internal List<RuntimeLibrary> RuntimeLibraryOrder { get; private set; }
internal RuntimeLibrary? LoadedLibrary { get; private set; }

public static RuntimeOptions Instance { get; } = new();

private RuntimeOptions()
{
BypassLoading = false;
LibraryPath = null;
RuntimeLibraryOrder = defaultRuntimeOrder;
}

/// <summary>
/// Sets a custom path to the Whisper native library.
/// Gets or sets a custom path to the Whisper native library.
/// </summary>
/// <remarks>
/// By default it is null and automatic path is inferred from the current platform.
/// </remarks>
public void SetLibraryPath(string? path)
{
LibraryPath = path;
}
public static string? LibraryPath { get; set; }

/// <summary>
/// Bypasses the automatic loading of the Whisper native library.
/// Gets or sets the order of the runtime libraries to use for processing.
/// </summary>
/// <remarks>
/// By default, it is false.
/// The default order is [RuntimeLibrary.Cuda, RuntimeLibrary.Vulkan, RuntimeLibrary.CoreML, RuntimeLibrary.OpenVino, RuntimeLibrary.Cpu, RuntimeLibrary.CpuNoAvx].
/// </remarks>
public void SetBypassLoading(bool bypass)
{
BypassLoading = bypass;
}
public static List<RuntimeLibrary> RuntimeLibraryOrder { get; set; } = defaultRuntimeOrder;

/// <summary>
/// Sets the order of the runtime libraries to use for processing.
/// Gets or sets the library that was loaded by the runtime.
/// </summary>
/// <remarks>
/// By default, it is [RuntimeLibrary.Cuda, RuntimeLibrary.Vulkan, RuntimeLibrary.CoreML, RuntimeLibrary.OpenVino, RuntimeLibrary.Cpu].
/// Setting a custom value will bypass the automatic loading of the Whisper native library.
/// If no custom value is used, the library will be loaded automatically based on the <see cref="RuntimeLibraryOrder"/> and the available libraries on the system.
/// Once a library is loaded, it will be used for all subsequent processing.
/// </remarks>
public void SetRuntimeLibraryOrder(List<RuntimeLibrary> order)
{
RuntimeLibraryOrder = order;
}

/// <summary>
/// Sets the loaded library if it was loaded manually.
/// </summary>
public void SetLoadedLibrary(RuntimeLibrary library)
{
LoadedLibrary = library;
}

/// <summary>
/// Resets the runtime options to their default values.
/// </summary>
public void Reset()
{
BypassLoading = false;
LibraryPath = null;
RuntimeLibraryOrder = defaultRuntimeOrder;
}
public static RuntimeLibrary? LoadedLibrary { get; set; }
}
43 changes: 20 additions & 23 deletions Whisper.net/Logger/LogProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,37 @@
using Whisper.net.Native;

namespace Whisper.net.Logger;
public class LogProvider
public static class LogProvider
{

private LogProvider()
{

}

/// <summary>
/// Returns the singleton instance of the <see cref="LogProvider"/> class used to log messages from the Whisper library.
/// </summary>
public static LogProvider Instance { get; } = new();

public event Action<WhisperLogLevel, string?>? OnLog;

/// <summary>
/// Adds a console logger that logs messages with a severity greater than or equal to the specified level.
/// </summary>
/// <param name="minLevel">The minimum severity level to log.</param>
public static void AddConsoleLogging(WhisperLogLevel minLevel = WhisperLogLevel.Info)
/// <returns>
/// Returns a disposable object that can be used to remove the logger.
/// </returns>
public static IDisposable AddConsoleLogging(WhisperLogLevel minLevel = WhisperLogLevel.Info)
{
Instance.OnLog += (level, message) =>
return new WhisperLogger((level, message) =>
{
// Higher values are less severe
if (level < minLevel)
{
Console.WriteLine($"[{level}] {message}");
}
};
});
}

/// <summary>
/// Adds a logger that logs messages with a custom action.
/// </summary>
/// <param name="logAction">The action to log.</param>
/// <returns>
/// Returns a disposable object that can be used to remove the logger.
/// </returns>
public static IDisposable AddLogger(Action<WhisperLogLevel, string?> logAction)
{
return new WhisperLogger(logAction);
}

internal static void InitializeLogging(INativeWhisper nativeWhisper)
Expand Down Expand Up @@ -67,11 +69,6 @@ internal static void LogUnmanaged(GgmlLogLevel level, IntPtr message, IntPtr use
_ => WhisperLogLevel.Info
};

Log(managedLevel, messageString);
}

internal static void Log(WhisperLogLevel level, string? message)
{
Instance.OnLog?.Invoke(level, message);
WhisperLogger.Log(managedLevel, messageString);
}
}
26 changes: 26 additions & 0 deletions Whisper.net/Logger/WhisperLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed under the MIT license: https://opensource.org/licenses/MIT

namespace Whisper.net.Logger;

internal class WhisperLogger : IDisposable
{
private readonly Action<WhisperLogLevel, string?> logAction;

public WhisperLogger(Action<WhisperLogLevel, string?> logAction)
{
this.logAction = logAction;
OnLog += logAction;
}

public static event Action<WhisperLogLevel, string?>? OnLog;

public void Dispose()
{
OnLog -= logAction;
}

public static void Log(WhisperLogLevel level, string? message)
{
OnLog?.Invoke(level, message);
}
}
Loading

0 comments on commit db4a246

Please sign in to comment.