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

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