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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  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 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. {
  141. throw new ArgumentException("Cannot skip the check when fallback is allowed.");
  142. }
  143. return new Description(
  144. Instance._libraryPath,
  145. Instance._useCuda,
  146. Instance._avxLevel,
  147. Instance._allowFallback,
  148. Instance._skipCheck,
  149. Instance._logging,
  150. Instance._searchDirectories.Concat(new string[] { "./" }).ToArray());
  151. }
  152. internal static string AvxLevelToString(AvxLevel level)
  153. {
  154. return level switch
  155. {
  156. AvxLevel.None => string.Empty,
  157. AvxLevel.Avx => "avx",
  158. AvxLevel.Avx2 => "avx2",
  159. AvxLevel.Avx512 => "avx512",
  160. _ => throw new ArgumentException($"Unknown AvxLevel '{level}'")
  161. };
  162. }
  163. /// <summary>
  164. /// Private constructor prevents new instances of this class being created
  165. /// </summary>
  166. private NativeLibraryConfig()
  167. {
  168. // Automatically detect the highest supported AVX level
  169. if (System.Runtime.Intrinsics.X86.Avx.IsSupported)
  170. _avxLevel = AvxLevel.Avx;
  171. if (System.Runtime.Intrinsics.X86.Avx2.IsSupported)
  172. _avxLevel = AvxLevel.Avx2;
  173. if (CheckAVX512())
  174. _avxLevel = AvxLevel.Avx512;
  175. }
  176. private static bool CheckAVX512()
  177. {
  178. if (!System.Runtime.Intrinsics.X86.X86Base.IsSupported)
  179. return false;
  180. var (_, ebx, ecx, _) = System.Runtime.Intrinsics.X86.X86Base.CpuId(7, 0);
  181. var vnni = (ecx & 0b_1000_0000_0000) != 0;
  182. #if NET8_0_OR_GREATER
  183. var f = System.Runtime.Intrinsics.X86.Avx512F.IsSupported;
  184. var bw = System.Runtime.Intrinsics.X86.Avx512BW.IsSupported;
  185. var vbmi = System.Runtime.Intrinsics.X86.Avx512Vbmi.IsSupported;
  186. #else
  187. var f = (ebx & (1 << 16)) != 0;
  188. var bw = (ebx & (1 << 30)) != 0;
  189. var vbmi = (ecx & 0b_0000_0000_0010) != 0;
  190. #endif
  191. return vnni && vbmi && bw && f;
  192. }
  193. /// <summary>
  194. /// Avx support configuration
  195. /// </summary>
  196. public enum AvxLevel
  197. {
  198. /// <summary>
  199. /// No AVX
  200. /// </summary>
  201. None,
  202. /// <summary>
  203. /// Advanced Vector Extensions (supported by most processors after 2011)
  204. /// </summary>
  205. Avx,
  206. /// <summary>
  207. /// AVX2 (supported by most processors after 2013)
  208. /// </summary>
  209. Avx2,
  210. /// <summary>
  211. /// AVX512 (supported by some processors after 2016, not widely supported)
  212. /// </summary>
  213. Avx512,
  214. }
  215. internal record Description(string Path, bool UseCuda, AvxLevel AvxLevel, bool AllowFallback, bool SkipCheck, bool Logging, string[] SearchDirectories)
  216. {
  217. public override string ToString()
  218. {
  219. string avxLevelString = AvxLevel switch
  220. {
  221. AvxLevel.None => "NoAVX",
  222. AvxLevel.Avx => "AVX",
  223. AvxLevel.Avx2 => "AVX2",
  224. AvxLevel.Avx512 => "AVX512",
  225. _ => "Unknown"
  226. };
  227. string searchDirectoriesString = "{ " + string.Join(", ", SearchDirectories) + " }";
  228. return $"NativeLibraryConfig Description:\n" +
  229. $"- Path: {Path}\n" +
  230. $"- PreferCuda: {UseCuda}\n" +
  231. $"- PreferredAvxLevel: {avxLevelString}\n" +
  232. $"- AllowFallback: {AllowFallback}\n" +
  233. $"- SkipCheck: {SkipCheck}\n" +
  234. $"- Logging: {Logging}\n" +
  235. $"- SearchDirectories and Priorities: {searchDirectoriesString}";
  236. }
  237. }
  238. }
  239. #endif
  240. }