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

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