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.

NativeLibraryConfig.cs 12 kB

1 year ago
1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Microsoft.Extensions.Logging;
  5. namespace LLama.Native
  6. {
  7. #if NET6_0_OR_GREATER
  8. /// <summary>
  9. /// Allows configuration of the native llama.cpp libraries to load and use.
  10. /// All configuration must be done before using **any** other LLamaSharp methods!
  11. /// </summary>
  12. public sealed partial class NativeLibraryConfig
  13. {
  14. private string? _libraryPath;
  15. private string? _libraryPathLLava;
  16. private bool _useCuda = true;
  17. private AvxLevel _avxLevel;
  18. private bool _allowFallback = true;
  19. private bool _skipCheck = false;
  20. /// <summary>
  21. /// search directory -> priority level, 0 is the lowest.
  22. /// </summary>
  23. private readonly List<string> _searchDirectories = new List<string>();
  24. #region configurators
  25. /// <summary>
  26. /// Load a specified native library as backend for LLamaSharp.
  27. /// When this method is called, all the other configurations will be ignored.
  28. /// </summary>
  29. /// <param name="llamaPath">The full path to the llama library to load.</param>
  30. /// <param name="llavaPath">The full path to the llava library to load.</param>
  31. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  32. public NativeLibraryConfig WithLibrary(string? llamaPath, string? llavaPath)
  33. {
  34. ThrowIfLoaded();
  35. _libraryPath = llamaPath;
  36. _libraryPathLLava = llavaPath;
  37. return this;
  38. }
  39. /// <summary>
  40. /// Configure whether to use cuda backend if possible.
  41. /// </summary>
  42. /// <param name="enable"></param>
  43. /// <returns></returns>
  44. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  45. public NativeLibraryConfig WithCuda(bool enable = true)
  46. {
  47. ThrowIfLoaded();
  48. _useCuda = enable;
  49. return this;
  50. }
  51. /// <summary>
  52. /// Configure the prefferred avx support level of the backend.
  53. /// </summary>
  54. /// <param name="level"></param>
  55. /// <returns></returns>
  56. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  57. public NativeLibraryConfig WithAvx(AvxLevel level)
  58. {
  59. ThrowIfLoaded();
  60. _avxLevel = level;
  61. return this;
  62. }
  63. /// <summary>
  64. /// Configure whether to allow fallback when there's no match for preferred settings.
  65. /// </summary>
  66. /// <param name="enable"></param>
  67. /// <returns></returns>
  68. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  69. public NativeLibraryConfig WithAutoFallback(bool enable = true)
  70. {
  71. ThrowIfLoaded();
  72. _allowFallback = enable;
  73. return this;
  74. }
  75. /// <summary>
  76. /// Whether to skip the check when you don't allow fallback. This option
  77. /// may be useful under some complex conditions. For example, you're sure
  78. /// you have your cublas configured but LLamaSharp take it as invalid by mistake.
  79. /// </summary>
  80. /// <param name="enable"></param>
  81. /// <returns></returns>
  82. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  83. public NativeLibraryConfig SkipCheck(bool enable = true)
  84. {
  85. ThrowIfLoaded();
  86. _skipCheck = enable;
  87. return this;
  88. }
  89. /// <summary>
  90. /// Add self-defined search directories. Note that the file structure of the added
  91. /// directories must be the same as the default directory. Besides, the directory
  92. /// won't be used recursively.
  93. /// </summary>
  94. /// <param name="directories"></param>
  95. /// <returns></returns>
  96. public NativeLibraryConfig WithSearchDirectories(IEnumerable<string> directories)
  97. {
  98. ThrowIfLoaded();
  99. _searchDirectories.AddRange(directories);
  100. return this;
  101. }
  102. /// <summary>
  103. /// Add self-defined search directories. Note that the file structure of the added
  104. /// directories must be the same as the default directory. Besides, the directory
  105. /// won't be used recursively.
  106. /// </summary>
  107. /// <param name="directory"></param>
  108. /// <returns></returns>
  109. public NativeLibraryConfig WithSearchDirectory(string directory)
  110. {
  111. ThrowIfLoaded();
  112. _searchDirectories.Add(directory);
  113. return this;
  114. }
  115. #endregion
  116. internal static Description CheckAndGatherDescription(LibraryName library)
  117. {
  118. if (Instance._allowFallback && Instance._skipCheck)
  119. throw new ArgumentException("Cannot skip the check when fallback is allowed.");
  120. var path = library switch
  121. {
  122. LibraryName.Llama => Instance._libraryPath,
  123. LibraryName.LlavaShared => Instance._libraryPathLLava,
  124. _ => throw new ArgumentException($"Unknown library name '{library}'", nameof(library)),
  125. };
  126. return new Description(
  127. path,
  128. library,
  129. Instance._useCuda,
  130. Instance._avxLevel,
  131. Instance._allowFallback,
  132. Instance._skipCheck,
  133. Instance._searchDirectories.Concat(new[] { "./" }).ToArray()
  134. );
  135. }
  136. internal static string AvxLevelToString(AvxLevel level)
  137. {
  138. return level switch
  139. {
  140. AvxLevel.None => string.Empty,
  141. AvxLevel.Avx => "avx",
  142. AvxLevel.Avx2 => "avx2",
  143. AvxLevel.Avx512 => "avx512",
  144. _ => throw new ArgumentException($"Unknown AvxLevel '{level}'")
  145. };
  146. }
  147. /// <summary>
  148. /// Private constructor prevents new instances of this class being created
  149. /// </summary>
  150. private NativeLibraryConfig()
  151. {
  152. // Automatically detect the highest supported AVX level
  153. if (System.Runtime.Intrinsics.X86.Avx.IsSupported)
  154. _avxLevel = AvxLevel.Avx;
  155. if (System.Runtime.Intrinsics.X86.Avx2.IsSupported)
  156. _avxLevel = AvxLevel.Avx2;
  157. if (CheckAVX512())
  158. _avxLevel = AvxLevel.Avx512;
  159. }
  160. private static bool CheckAVX512()
  161. {
  162. if (!System.Runtime.Intrinsics.X86.X86Base.IsSupported)
  163. return false;
  164. // ReSharper disable UnusedVariable (ebx is used when < NET8)
  165. var (_, ebx, ecx, _) = System.Runtime.Intrinsics.X86.X86Base.CpuId(7, 0);
  166. // ReSharper restore UnusedVariable
  167. var vnni = (ecx & 0b_1000_0000_0000) != 0;
  168. #if NET8_0_OR_GREATER
  169. var f = System.Runtime.Intrinsics.X86.Avx512F.IsSupported;
  170. var bw = System.Runtime.Intrinsics.X86.Avx512BW.IsSupported;
  171. var vbmi = System.Runtime.Intrinsics.X86.Avx512Vbmi.IsSupported;
  172. #else
  173. var f = (ebx & (1 << 16)) != 0;
  174. var bw = (ebx & (1 << 30)) != 0;
  175. var vbmi = (ecx & 0b_0000_0000_0010) != 0;
  176. #endif
  177. return vnni && vbmi && bw && f;
  178. }
  179. /// <summary>
  180. /// Avx support configuration
  181. /// </summary>
  182. public enum AvxLevel
  183. {
  184. /// <summary>
  185. /// No AVX
  186. /// </summary>
  187. None,
  188. /// <summary>
  189. /// Advanced Vector Extensions (supported by most processors after 2011)
  190. /// </summary>
  191. Avx,
  192. /// <summary>
  193. /// AVX2 (supported by most processors after 2013)
  194. /// </summary>
  195. Avx2,
  196. /// <summary>
  197. /// AVX512 (supported by some processors after 2016, not widely supported)
  198. /// </summary>
  199. Avx512,
  200. }
  201. internal record Description(string? Path, LibraryName Library, bool UseCuda, AvxLevel AvxLevel, bool AllowFallback, bool SkipCheck, string[] SearchDirectories)
  202. {
  203. public override string ToString()
  204. {
  205. string avxLevelString = AvxLevel switch
  206. {
  207. AvxLevel.None => "NoAVX",
  208. AvxLevel.Avx => "AVX",
  209. AvxLevel.Avx2 => "AVX2",
  210. AvxLevel.Avx512 => "AVX512",
  211. _ => "Unknown"
  212. };
  213. string searchDirectoriesString = "{ " + string.Join(", ", SearchDirectories) + " }";
  214. return $"NativeLibraryConfig Description:\n" +
  215. $"- LibraryName: {Library}\n" +
  216. $"- Path: '{Path}'\n" +
  217. $"- PreferCuda: {UseCuda}\n" +
  218. $"- PreferredAvxLevel: {avxLevelString}\n" +
  219. $"- AllowFallback: {AllowFallback}\n" +
  220. $"- SkipCheck: {SkipCheck}\n" +
  221. $"- SearchDirectories and Priorities: {searchDirectoriesString}";
  222. }
  223. }
  224. }
  225. #endif
  226. public sealed partial class NativeLibraryConfig
  227. {
  228. /// <summary>
  229. /// Get the config instance
  230. /// </summary>
  231. public static NativeLibraryConfig Instance { get; } = new();
  232. /// <summary>
  233. /// Check if the native library has already been loaded. Configuration cannot be modified if this is true.
  234. /// </summary>
  235. public static bool LibraryHasLoaded { get; internal set; }
  236. internal NativeLogConfig.LLamaLogCallback? LogCallback;
  237. private static void ThrowIfLoaded()
  238. {
  239. if (LibraryHasLoaded)
  240. throw new InvalidOperationException("NativeLibraryConfig must be configured before using **any** other LLamaSharp methods!");
  241. }
  242. /// <summary>
  243. /// Set the log callback that will be used for all llama.cpp log messages
  244. /// </summary>
  245. /// <param name="callback"></param>
  246. /// <exception cref="NotImplementedException"></exception>
  247. public NativeLibraryConfig WithLogCallback(NativeLogConfig.LLamaLogCallback? callback)
  248. {
  249. ThrowIfLoaded();
  250. LogCallback = callback;
  251. return this;
  252. }
  253. /// <summary>
  254. /// Set the log callback that will be used for all llama.cpp log messages
  255. /// </summary>
  256. /// <param name="logger"></param>
  257. /// <exception cref="NotImplementedException"></exception>
  258. public NativeLibraryConfig WithLogCallback(ILogger? logger)
  259. {
  260. ThrowIfLoaded();
  261. // Redirect to llama_log_set. This will wrap the logger in a delegate and bind that as the log callback instead.
  262. NativeLogConfig.llama_log_set(logger);
  263. return this;
  264. }
  265. }
  266. internal enum LibraryName
  267. {
  268. Llama,
  269. LlavaShared
  270. }
  271. internal static class LibraryNameExtensions
  272. {
  273. public static string GetLibraryName(this LibraryName name)
  274. {
  275. switch (name)
  276. {
  277. case LibraryName.Llama:
  278. return NativeApi.libraryName;
  279. case LibraryName.LlavaShared:
  280. return NativeApi.llavaLibraryName;
  281. default:
  282. throw new ArgumentOutOfRangeException(nameof(name), name, null);
  283. }
  284. }
  285. }
  286. }