using LLama.Exceptions; using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; namespace LLama.Native { internal static class NativeLibraryUtils { /// /// Try to load libllama/llava_shared, using CPU feature detection to try and load a more specialised DLL if possible /// /// The library handle to unload later, or IntPtr.Zero if no library was loaded internal static IntPtr TryLoadLibrary(NativeLibraryConfig config) { #if NET6_0_OR_GREATER var description = config.CheckAndGatherDescription(); var systemInfo = SystemInfo.Get(); Log($"Loading library: '{config.NativeLibraryName.GetLibraryName()}'", LLamaLogLevel.Debug, config.LogCallback); // Get platform specific parts of the path (e.g. .so/.dll/.dylib, libName prefix or not) NativeLibraryUtils.GetPlatformPathParts(systemInfo.OSPlatform, out var os, out var ext, out var libPrefix); Log($"Detected OS Platform: '{systemInfo.OSPlatform}'", LLamaLogLevel.Info, config.LogCallback); Log($"Detected OS string: '{os}'", LLamaLogLevel.Debug, config.LogCallback); Log($"Detected extension string: '{ext}'", LLamaLogLevel.Debug, config.LogCallback); Log($"Detected prefix string: '{libPrefix}'", LLamaLogLevel.Debug, config.LogCallback); // Set the flag to ensure this config can no longer be modified config.LibraryHasLoaded = true; // Show the configuration we're working with Log(description.ToString(), LLamaLogLevel.Info, config.LogCallback); // Get the libraries ordered by priority from the selecting policy. var libraries = config.SelectingPolicy.Apply(description, systemInfo, config.LogCallback); foreach (var library in libraries) { // Prepare the local library file and get the path. var paths = library.Prepare(systemInfo, config.LogCallback); foreach (var path in paths) { Log($"Got relative library path '{path}' from local with {library.Metadata}, trying to load it...", LLamaLogLevel.Debug, config.LogCallback); var result = TryLoad(path, description.SearchDirectories, config.LogCallback); if (result != IntPtr.Zero) { return result; } } } // If fallback is allowed, we will make the last try (the default system loading) when calling the native api. // Otherwise we throw an exception here. if (!description.AllowFallback) { throw new RuntimeError("Failed to load the native library. Please check the log for more information."); } #endif Log($"No library was loaded before calling native apis. " + $"This is not an error under netstandard2.0 but needs attention with net6 or higher.", LLamaLogLevel.Warning, config.LogCallback); return IntPtr.Zero; #if NET6_0_OR_GREATER // Try to load a DLL from the path. // Returns null if nothing is loaded. static IntPtr TryLoad(string path, IEnumerable searchDirectories, NativeLogConfig.LLamaLogCallback? logCallback) { var fullPath = TryFindPath(path, searchDirectories); Log($"Found full path file '{fullPath}' for relative path '{path}'", LLamaLogLevel.Debug, logCallback); if (NativeLibrary.TryLoad(fullPath, out var handle)) { Log($"Successfully loaded '{fullPath}'", LLamaLogLevel.Info, logCallback); return handle; } Log($"Failed Loading '{fullPath}'", LLamaLogLevel.Info, logCallback); return IntPtr.Zero; } #endif } // Try to find the given file in any of the possible search paths private static string TryFindPath(string filename, IEnumerable searchDirectories) { // Try the configured search directories in the configuration foreach (var path in searchDirectories) { var candidate = Path.Combine(path, filename); if (File.Exists(candidate)) return candidate; } // Try a few other possible paths var possiblePathPrefix = new[] { AppDomain.CurrentDomain.BaseDirectory, Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? "" }; foreach (var path in possiblePathPrefix) { var candidate = Path.Combine(path, filename); if (File.Exists(candidate)) return candidate; } return filename; } private static void Log(string message, LLamaLogLevel level, NativeLogConfig.LLamaLogCallback? logCallback) { if (!message.EndsWith("\n")) message += "\n"; logCallback?.Invoke(level, message); } #if NET6_0_OR_GREATER public static void GetPlatformPathParts(OSPlatform platform, out string os, out string fileExtension, out string libPrefix) { if (platform == OSPlatform.Windows) { os = "win-x64"; fileExtension = ".dll"; libPrefix = ""; return; } if (platform == OSPlatform.Linux) { os = "linux-x64"; fileExtension = ".so"; libPrefix = "lib"; return; } if (platform == OSPlatform.OSX) { fileExtension = ".dylib"; os = System.Runtime.Intrinsics.Arm.ArmBase.Arm64.IsSupported ? "osx-arm64" : "osx-x64"; libPrefix = "lib"; } else { throw new RuntimeError("Your operating system is not supported, please open an issue in LLamaSharp."); } } #endif } }