You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

NativeApi.Load.cs 14 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. using LLama.Exceptions;
  2. using Microsoft.Extensions.Logging;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Runtime.InteropServices;
  9. using System.Text.Json;
  10. namespace LLama.Native
  11. {
  12. public partial class NativeApi
  13. {
  14. static NativeApi()
  15. {
  16. // Try to load a preferred library, based on CPU feature detection
  17. TryLoadLibrary();
  18. try
  19. {
  20. llama_empty_call();
  21. }
  22. catch (DllNotFoundException)
  23. {
  24. throw new RuntimeError("The native library cannot be correctly loaded. It could be one of the following reasons: \n" +
  25. "1. No LLamaSharp backend was installed. Please search LLamaSharp.Backend and install one of them. \n" +
  26. "2. You are using a device with only CPU but installed cuda backend. Please install cpu backend instead. \n" +
  27. "3. One of the dependency of the native library is missed. Please use `ldd` on linux, `dumpbin` on windows and `otool`" +
  28. "to check if all the dependency of the native library is satisfied. Generally you could find the libraries under your output folder.\n" +
  29. "4. Try to compile llama.cpp yourself to generate a libllama library, then use `LLama.Native.NativeLibraryConfig.WithLibrary` " +
  30. "to specify it at the very beginning of your code. For more informations about compilation, please refer to LLamaSharp repo on github.\n");
  31. }
  32. llama_backend_init(false);
  33. }
  34. private static void Log(string message, LogLevel level)
  35. {
  36. if (!enableLogging) return;
  37. Debug.Assert(level is LogLevel.Information or LogLevel.Error or LogLevel.Warning);
  38. ConsoleColor color;
  39. string levelPrefix;
  40. if (level == LogLevel.Information)
  41. {
  42. color = ConsoleColor.Green;
  43. levelPrefix = "[Info]";
  44. }
  45. else if (level == LogLevel.Error)
  46. {
  47. color = ConsoleColor.Red;
  48. levelPrefix = "[Error]";
  49. }
  50. else
  51. {
  52. color = ConsoleColor.Yellow;
  53. levelPrefix = "[Error]";
  54. }
  55. Console.ForegroundColor = color;
  56. Console.WriteLine($"{loggingPrefix} {levelPrefix} {message}");
  57. Console.ResetColor();
  58. }
  59. private static int GetCudaMajorVersion()
  60. {
  61. string? cudaPath;
  62. string version = "";
  63. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  64. {
  65. cudaPath = Environment.GetEnvironmentVariable("CUDA_PATH");
  66. if (cudaPath is null)
  67. {
  68. return -1;
  69. }
  70. version = GetCudaVersionFromPath(cudaPath);
  71. }
  72. else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
  73. {
  74. // Try the default first
  75. cudaPath = "/usr/local/bin/cuda";
  76. version = GetCudaVersionFromPath(cudaPath);
  77. if (string.IsNullOrEmpty(version))
  78. {
  79. cudaPath = Environment.GetEnvironmentVariable("LD_LIBRARY_PATH");
  80. if (cudaPath is null)
  81. {
  82. return -1;
  83. }
  84. foreach (var path in cudaPath.Split(':'))
  85. {
  86. version = GetCudaVersionFromPath(Path.Combine(path, ".."));
  87. if (string.IsNullOrEmpty(version))
  88. {
  89. break;
  90. }
  91. }
  92. }
  93. }
  94. if (string.IsNullOrEmpty(version))
  95. {
  96. return -1;
  97. }
  98. else
  99. {
  100. version = version.Split('.')[0];
  101. bool success = int.TryParse(version, out var majorVersion);
  102. if (success)
  103. {
  104. return majorVersion;
  105. }
  106. else
  107. {
  108. return -1;
  109. }
  110. }
  111. }
  112. private static string GetCudaVersionFromPath(string cudaPath)
  113. {
  114. try
  115. {
  116. string json = File.ReadAllText(Path.Combine(cudaPath, cudaVersionFile));
  117. using (JsonDocument document = JsonDocument.Parse(json))
  118. {
  119. JsonElement root = document.RootElement;
  120. JsonElement cublasNode = root.GetProperty("libcublas");
  121. JsonElement versionNode = cublasNode.GetProperty("version");
  122. if (versionNode.ValueKind == JsonValueKind.Undefined)
  123. {
  124. return string.Empty;
  125. }
  126. return versionNode.GetString();
  127. }
  128. }
  129. catch (Exception)
  130. {
  131. return string.Empty;
  132. }
  133. }
  134. #if NET6_0_OR_GREATER
  135. private static string GetAvxLibraryPath(NativeLibraryConfig.AvxLevel avxLevel, string prefix, string suffix)
  136. {
  137. var avxStr = NativeLibraryConfig.AvxLevelToString(avxLevel);
  138. if (!string.IsNullOrEmpty(avxStr))
  139. {
  140. avxStr += "/";
  141. }
  142. return $"{prefix}{avxStr}{libraryName}{suffix}";
  143. }
  144. private static List<string> GetLibraryTryOrder(NativeLibraryConfig.Description configuration)
  145. {
  146. OSPlatform platform;
  147. string prefix, suffix;
  148. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  149. {
  150. platform = OSPlatform.Windows;
  151. prefix = "runtimes/win-x64/native/";
  152. suffix = ".dll";
  153. }
  154. else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
  155. {
  156. platform = OSPlatform.Linux;
  157. prefix = "runtimes/linux-x64/native/";
  158. suffix = ".so";
  159. }
  160. else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
  161. {
  162. platform = OSPlatform.OSX;
  163. suffix = ".dylib";
  164. if (System.Runtime.Intrinsics.Arm.ArmBase.Arm64.IsSupported)
  165. {
  166. prefix = "runtimes/osx-arm64/native/";
  167. }
  168. else
  169. {
  170. prefix = "runtimes/osx-x64/native/";
  171. }
  172. }
  173. else
  174. {
  175. throw new RuntimeError($"Your system plarform is not supported, please open an issue in LLamaSharp.");
  176. }
  177. Log($"Detected OS Platform: {platform}", LogLevel.Information);
  178. List<string> result = new();
  179. if (configuration.UseCuda && (platform == OSPlatform.Windows || platform == OSPlatform.Linux)) // no cuda on macos
  180. {
  181. int cudaVersion = GetCudaMajorVersion();
  182. // TODO: load cuda library with avx
  183. if (cudaVersion == -1 && !configuration.AllowFallback)
  184. {
  185. // if check skipped, we just try to load cuda libraries one by one.
  186. if (configuration.SkipCheck)
  187. {
  188. result.Add($"{prefix}cuda12/{libraryName}{suffix}");
  189. result.Add($"{prefix}cuda11/{libraryName}{suffix}");
  190. }
  191. else
  192. {
  193. throw new RuntimeError("Configured to load a cuda library but no cuda detected on your device.");
  194. }
  195. }
  196. else if (cudaVersion == 11)
  197. {
  198. Log($"Detected cuda major version {cudaVersion}.", LogLevel.Information);
  199. result.Add($"{prefix}cuda11/{libraryName}{suffix}");
  200. }
  201. else if (cudaVersion == 12)
  202. {
  203. Log($"Detected cuda major version {cudaVersion}.", LogLevel.Information);
  204. result.Add($"{prefix}cuda12/{libraryName}{suffix}");
  205. }
  206. else if (cudaVersion > 0)
  207. {
  208. throw new RuntimeError($"Cuda version {cudaVersion} hasn't been supported by LLamaSharp, please open an issue for it.");
  209. }
  210. // otherwise no cuda detected but allow fallback
  211. }
  212. // use cpu (or mac possibly with metal)
  213. if (!configuration.AllowFallback && platform != OSPlatform.OSX)
  214. {
  215. result.Add(GetAvxLibraryPath(configuration.AvxLevel, prefix, suffix));
  216. }
  217. else if (platform != OSPlatform.OSX) // in macos there's absolutely no avx
  218. {
  219. if (configuration.AvxLevel >= NativeLibraryConfig.AvxLevel.Avx512)
  220. result.Add(GetAvxLibraryPath(NativeLibraryConfig.AvxLevel.Avx512, prefix, suffix));
  221. if (configuration.AvxLevel >= NativeLibraryConfig.AvxLevel.Avx2)
  222. result.Add(GetAvxLibraryPath(NativeLibraryConfig.AvxLevel.Avx2, prefix, suffix));
  223. if (configuration.AvxLevel >= NativeLibraryConfig.AvxLevel.Avx)
  224. result.Add(GetAvxLibraryPath(NativeLibraryConfig.AvxLevel.Avx, prefix, suffix));
  225. result.Add(GetAvxLibraryPath(NativeLibraryConfig.AvxLevel.None, prefix, suffix));
  226. }
  227. if (platform == OSPlatform.OSX)
  228. {
  229. result.Add($"{prefix}{libraryName}{suffix}");
  230. }
  231. return result;
  232. }
  233. #endif
  234. /// <summary>
  235. /// Try to load libllama, using CPU feature detection to try and load a more specialised DLL if possible
  236. /// </summary>
  237. /// <returns>The library handle to unload later, or IntPtr.Zero if no library was loaded</returns>
  238. private static IntPtr TryLoadLibrary()
  239. {
  240. #if NET6_0_OR_GREATER
  241. var configuration = NativeLibraryConfig.CheckAndGatherDescription();
  242. enableLogging = configuration.Logging;
  243. // We move the flag to avoid loading library when the variable is called else where.
  244. NativeLibraryConfig.LibraryHasLoaded = true;
  245. Log(configuration.ToString(), LogLevel.Information);
  246. if (!string.IsNullOrEmpty(configuration.Path))
  247. {
  248. // When loading the user specified library, there's no fallback.
  249. var success = NativeLibrary.TryLoad(configuration.Path, out var result);
  250. if (!success)
  251. {
  252. throw new RuntimeError($"Failed to load the native library [{configuration.Path}] you specified.");
  253. }
  254. Log($"Successfully loaded the library [{configuration.Path}] specified by user", LogLevel.Information);
  255. return result;
  256. }
  257. var libraryTryLoadOrder = GetLibraryTryOrder(configuration);
  258. string[] preferredPaths = configuration.SearchDirectories;
  259. string[] possiblePathPrefix = new string[] {
  260. System.AppDomain.CurrentDomain.BaseDirectory,
  261. Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? ""
  262. };
  263. var tryFindPath = (string filename) =>
  264. {
  265. foreach(var path in preferredPaths)
  266. {
  267. if (File.Exists(Path.Combine(path, filename)))
  268. {
  269. return Path.Combine(path, filename);
  270. }
  271. }
  272. foreach(var path in possiblePathPrefix)
  273. {
  274. if (File.Exists(Path.Combine(path, filename)))
  275. {
  276. return Path.Combine(path, filename);
  277. }
  278. }
  279. return filename;
  280. };
  281. foreach (var libraryPath in libraryTryLoadOrder)
  282. {
  283. var fullPath = tryFindPath(libraryPath);
  284. var result = TryLoad(fullPath, true);
  285. if (result is not null && result != IntPtr.Zero)
  286. {
  287. Log($"{fullPath} is selected and loaded successfully.", LogLevel.Information);
  288. return result ?? IntPtr.Zero;
  289. }
  290. else
  291. {
  292. Log($"Tried to load {fullPath} but failed.", LogLevel.Information);
  293. }
  294. }
  295. if (!configuration.AllowFallback)
  296. {
  297. throw new RuntimeError("Failed to load the library that match your rule, please" +
  298. " 1) check your rule." +
  299. " 2) try to allow fallback." +
  300. " 3) or open an issue if it's expected to be successful.");
  301. }
  302. #endif
  303. Log($"No library was loaded before calling native apis. " +
  304. $"This is not an error under netstandard2.0 but needs attention with net6 or higher.", LogLevel.Warning);
  305. return IntPtr.Zero;
  306. #if NET6_0_OR_GREATER
  307. // Try to load a DLL from the path if supported. Returns null if nothing is loaded.
  308. static IntPtr? TryLoad(string path, bool supported = true)
  309. {
  310. if (!supported)
  311. return null;
  312. if (NativeLibrary.TryLoad(path, out var handle))
  313. return handle;
  314. return null;
  315. }
  316. #endif
  317. }
  318. private const string libraryName = "libllama";
  319. private const string cudaVersionFile = "version.json";
  320. private const string loggingPrefix = "[LLamaSharp Native]";
  321. private static bool enableLogging = false;
  322. }
  323. }