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
}
}