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 10 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. private static readonly Lazy<NativeLibraryConfig> _instance = new(() => new NativeLibraryConfig());
  14. /// <summary>
  15. /// Get the config instance
  16. /// </summary>
  17. public static NativeLibraryConfig Instance => _instance.Value;
  18. /// <summary>
  19. /// Whether there's already a config for native library.
  20. /// </summary>
  21. public static bool LibraryHasLoaded { get; internal set; } = false;
  22. private string _libraryPath = string.Empty;
  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. /// <summary>
  29. /// search directory -> priority level, 0 is the lowest.
  30. /// </summary>
  31. private readonly List<string> _searchDirectories = new List<string>();
  32. private static void ThrowIfLoaded()
  33. {
  34. if (LibraryHasLoaded)
  35. throw new InvalidOperationException("NativeLibraryConfig must be configured before using **any** other LLamaSharp methods!");
  36. }
  37. /// <summary>
  38. /// Load a specified native library as backend for LLamaSharp.
  39. /// When this method is called, all the other configurations will be ignored.
  40. /// </summary>
  41. /// <param name="libraryPath"></param>
  42. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  43. public NativeLibraryConfig WithLibrary(string libraryPath)
  44. {
  45. ThrowIfLoaded();
  46. _libraryPath = libraryPath;
  47. return this;
  48. }
  49. /// <summary>
  50. /// Configure whether to use cuda backend if possible.
  51. /// </summary>
  52. /// <param name="enable"></param>
  53. /// <returns></returns>
  54. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  55. public NativeLibraryConfig WithCuda(bool enable = true)
  56. {
  57. ThrowIfLoaded();
  58. _useCuda = enable;
  59. return this;
  60. }
  61. /// <summary>
  62. /// Configure the prefferred avx support level of the backend.
  63. /// </summary>
  64. /// <param name="level"></param>
  65. /// <returns></returns>
  66. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  67. public NativeLibraryConfig WithAvx(AvxLevel level)
  68. {
  69. ThrowIfLoaded();
  70. _avxLevel = level;
  71. return this;
  72. }
  73. /// <summary>
  74. /// Configure whether to allow fallback when there's no match for preferred settings.
  75. /// </summary>
  76. /// <param name="enable"></param>
  77. /// <returns></returns>
  78. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  79. public NativeLibraryConfig WithAutoFallback(bool enable = true)
  80. {
  81. ThrowIfLoaded();
  82. _allowFallback = enable;
  83. return this;
  84. }
  85. /// <summary>
  86. /// Whether to skip the check when you don't allow fallback. This option
  87. /// may be useful under some complex conditions. For example, you're sure
  88. /// you have your cublas configured but LLamaSharp take it as invalid by mistake.
  89. /// </summary>
  90. /// <param name="enable"></param>
  91. /// <returns></returns>
  92. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  93. public NativeLibraryConfig SkipCheck(bool enable = true)
  94. {
  95. ThrowIfLoaded();
  96. _skipCheck = enable;
  97. return this;
  98. }
  99. /// <summary>
  100. /// Whether to output the logs to console when loading the native library with your configuration.
  101. /// </summary>
  102. /// <param name="enable"></param>
  103. /// <returns></returns>
  104. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  105. public NativeLibraryConfig WithLogs(bool enable = true)
  106. {
  107. ThrowIfLoaded();
  108. _logging = enable;
  109. return this;
  110. }
  111. /// <summary>
  112. /// Add self-defined search directories. Note that the file stucture of the added
  113. /// directories must be the same as the default directory. Besides, the directory
  114. /// won't be used recursively.
  115. /// </summary>
  116. /// <param name="directories"></param>
  117. /// <returns></returns>
  118. public NativeLibraryConfig WithSearchDirectories(IEnumerable<string> directories)
  119. {
  120. ThrowIfLoaded();
  121. _searchDirectories.AddRange(directories);
  122. return this;
  123. }
  124. /// <summary>
  125. /// Add self-defined search directories. Note that the file stucture of the added
  126. /// directories must be the same as the default directory. Besides, the directory
  127. /// won't be used recursively.
  128. /// </summary>
  129. /// <param name="directory"></param>
  130. /// <returns></returns>
  131. public NativeLibraryConfig WithSearchDirectory(string directory)
  132. {
  133. ThrowIfLoaded();
  134. _searchDirectories.Add(directory);
  135. return this;
  136. }
  137. internal static Description CheckAndGatherDescription()
  138. {
  139. if (Instance._allowFallback && Instance._skipCheck)
  140. throw new ArgumentException("Cannot skip the check when fallback is allowed.");
  141. return new Description(
  142. Instance._libraryPath,
  143. Instance._useCuda,
  144. Instance._avxLevel,
  145. Instance._allowFallback,
  146. Instance._skipCheck,
  147. Instance._logging,
  148. Instance._searchDirectories.Concat(new[] { "./" }).ToArray()
  149. );
  150. }
  151. internal static string AvxLevelToString(AvxLevel level)
  152. {
  153. return level switch
  154. {
  155. AvxLevel.None => string.Empty,
  156. AvxLevel.Avx => "avx",
  157. AvxLevel.Avx2 => "avx2",
  158. AvxLevel.Avx512 => "avx512",
  159. _ => throw new ArgumentException($"Unknown AvxLevel '{level}'")
  160. };
  161. }
  162. /// <summary>
  163. /// Private constructor prevents new instances of this class being created
  164. /// </summary>
  165. private NativeLibraryConfig()
  166. {
  167. // Automatically detect the highest supported AVX level
  168. if (System.Runtime.Intrinsics.X86.Avx.IsSupported)
  169. _avxLevel = AvxLevel.Avx;
  170. if (System.Runtime.Intrinsics.X86.Avx2.IsSupported)
  171. _avxLevel = AvxLevel.Avx2;
  172. if (CheckAVX512())
  173. _avxLevel = AvxLevel.Avx512;
  174. }
  175. private static bool CheckAVX512()
  176. {
  177. if (!System.Runtime.Intrinsics.X86.X86Base.IsSupported)
  178. return false;
  179. // ReSharper disable UnusedVariable (ebx is used when < NET8)
  180. var (_, ebx, ecx, _) = System.Runtime.Intrinsics.X86.X86Base.CpuId(7, 0);
  181. // ReSharper restore UnusedVariable
  182. var vnni = (ecx & 0b_1000_0000_0000) != 0;
  183. #if NET8_0_OR_GREATER
  184. var f = System.Runtime.Intrinsics.X86.Avx512F.IsSupported;
  185. var bw = System.Runtime.Intrinsics.X86.Avx512BW.IsSupported;
  186. var vbmi = System.Runtime.Intrinsics.X86.Avx512Vbmi.IsSupported;
  187. #else
  188. var f = (ebx & (1 << 16)) != 0;
  189. var bw = (ebx & (1 << 30)) != 0;
  190. var vbmi = (ecx & 0b_0000_0000_0010) != 0;
  191. #endif
  192. return vnni && vbmi && bw && f;
  193. }
  194. /// <summary>
  195. /// Avx support configuration
  196. /// </summary>
  197. public enum AvxLevel
  198. {
  199. /// <summary>
  200. /// No AVX
  201. /// </summary>
  202. None,
  203. /// <summary>
  204. /// Advanced Vector Extensions (supported by most processors after 2011)
  205. /// </summary>
  206. Avx,
  207. /// <summary>
  208. /// AVX2 (supported by most processors after 2013)
  209. /// </summary>
  210. Avx2,
  211. /// <summary>
  212. /// AVX512 (supported by some processors after 2016, not widely supported)
  213. /// </summary>
  214. Avx512,
  215. }
  216. internal record Description(string Path, bool UseCuda, AvxLevel AvxLevel, bool AllowFallback, bool SkipCheck, bool Logging, string[] SearchDirectories)
  217. {
  218. public override string ToString()
  219. {
  220. string avxLevelString = AvxLevel switch
  221. {
  222. AvxLevel.None => "NoAVX",
  223. AvxLevel.Avx => "AVX",
  224. AvxLevel.Avx2 => "AVX2",
  225. AvxLevel.Avx512 => "AVX512",
  226. _ => "Unknown"
  227. };
  228. string searchDirectoriesString = "{ " + string.Join(", ", SearchDirectories) + " }";
  229. return $"NativeLibraryConfig Description:\n" +
  230. $"- Path: {Path}\n" +
  231. $"- PreferCuda: {UseCuda}\n" +
  232. $"- PreferredAvxLevel: {avxLevelString}\n" +
  233. $"- AllowFallback: {AllowFallback}\n" +
  234. $"- SkipCheck: {SkipCheck}\n" +
  235. $"- Logging: {Logging}\n" +
  236. $"- SearchDirectories and Priorities: {searchDirectoriesString}";
  237. }
  238. }
  239. }
  240. #endif
  241. }