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

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