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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using LLama.Abstractions;
  5. using Microsoft.Extensions.Logging;
  6. namespace LLama.Native
  7. {
  8. #if NET6_0_OR_GREATER
  9. /// <summary>
  10. /// Allows configuration of the native llama.cpp libraries to load and use.
  11. /// All configuration must be done before using **any** other LLamaSharp methods!
  12. /// </summary>
  13. public sealed partial class NativeLibraryConfig
  14. {
  15. private string? _libraryPath;
  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. internal INativeLibrarySelectingPolicy SelectingPolicy { get; private set; } = new DefaultNativeLibrarySelectingPolicy();
  25. #region configurators
  26. /// <summary>
  27. /// Load a specified native library as backend for LLamaSharp.
  28. /// When this method is called, all the other configurations will be ignored.
  29. /// </summary>
  30. /// <param name="libraryPath">The full path to the native library to load.</param>
  31. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  32. public NativeLibraryConfig WithLibrary(string? libraryPath)
  33. {
  34. ThrowIfLoaded();
  35. _libraryPath = libraryPath;
  36. return this;
  37. }
  38. /// <summary>
  39. /// Configure whether to use cuda backend if possible. Default is true.
  40. /// </summary>
  41. /// <param name="enable"></param>
  42. /// <returns></returns>
  43. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  44. public NativeLibraryConfig WithCuda(bool enable = true)
  45. {
  46. ThrowIfLoaded();
  47. _useCuda = enable;
  48. return this;
  49. }
  50. /// <summary>
  51. /// Configure the prefferred avx support level of the backend.
  52. /// Default value is detected automatically due to your operating system.
  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. Default is true.
  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. Default is false;
  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 stucture 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 stucture 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. /// <summary>
  116. /// Set the policy which decides how to select the desired native libraries and order them by priority.
  117. /// By default we use <see cref="DefaultNativeLibrarySelectingPolicy"/>.
  118. /// </summary>
  119. /// <param name="policy"></param>
  120. /// <returns></returns>
  121. public NativeLibraryConfig WithSelectingPolicy(INativeLibrarySelectingPolicy policy)
  122. {
  123. ThrowIfLoaded();
  124. SelectingPolicy = policy;
  125. return this;
  126. }
  127. #endregion
  128. internal Description CheckAndGatherDescription()
  129. {
  130. if (_allowFallback && _skipCheck)
  131. throw new ArgumentException("Cannot skip the check when fallback is allowed.");
  132. var path = _libraryPath;
  133. return new Description(
  134. path,
  135. NativeLibraryName,
  136. _useCuda,
  137. _avxLevel,
  138. _allowFallback,
  139. _skipCheck,
  140. _searchDirectories.Concat(new[] { "./" }).ToArray()
  141. );
  142. }
  143. internal static string AvxLevelToString(AvxLevel level)
  144. {
  145. return level switch
  146. {
  147. AvxLevel.None => string.Empty,
  148. AvxLevel.Avx => "avx",
  149. AvxLevel.Avx2 => "avx2",
  150. AvxLevel.Avx512 => "avx512",
  151. _ => throw new ArgumentException($"Unknown AvxLevel '{level}'")
  152. };
  153. }
  154. /// <summary>
  155. /// Private constructor prevents new instances of this class being created
  156. /// </summary>
  157. private NativeLibraryConfig(NativeLibraryName nativeLibraryName)
  158. {
  159. NativeLibraryName = nativeLibraryName;
  160. // Automatically detect the highest supported AVX level
  161. if (System.Runtime.Intrinsics.X86.Avx.IsSupported)
  162. _avxLevel = AvxLevel.Avx;
  163. if (System.Runtime.Intrinsics.X86.Avx2.IsSupported)
  164. _avxLevel = AvxLevel.Avx2;
  165. if (CheckAVX512())
  166. _avxLevel = AvxLevel.Avx512;
  167. }
  168. private static bool CheckAVX512()
  169. {
  170. if (!System.Runtime.Intrinsics.X86.X86Base.IsSupported)
  171. return false;
  172. // ReSharper disable UnusedVariable (ebx is used when < NET8)
  173. var (_, ebx, ecx, _) = System.Runtime.Intrinsics.X86.X86Base.CpuId(7, 0);
  174. // ReSharper restore UnusedVariable
  175. var vnni = (ecx & 0b_1000_0000_0000) != 0;
  176. #if NET8_0_OR_GREATER
  177. var f = System.Runtime.Intrinsics.X86.Avx512F.IsSupported;
  178. var bw = System.Runtime.Intrinsics.X86.Avx512BW.IsSupported;
  179. var vbmi = System.Runtime.Intrinsics.X86.Avx512Vbmi.IsSupported;
  180. #else
  181. var f = (ebx & (1 << 16)) != 0;
  182. var bw = (ebx & (1 << 30)) != 0;
  183. var vbmi = (ecx & 0b_0000_0000_0010) != 0;
  184. #endif
  185. return vnni && vbmi && bw && f;
  186. }
  187. /// <summary>
  188. /// The description of the native library configurations that's already specified.
  189. /// </summary>
  190. /// <param name="Path"></param>
  191. /// <param name="Library"></param>
  192. /// <param name="UseCuda"></param>
  193. /// <param name="AvxLevel"></param>
  194. /// <param name="AllowFallback"></param>
  195. /// <param name="SkipCheck"></param>
  196. /// <param name="SearchDirectories"></param>
  197. public record Description(string? Path, NativeLibraryName Library, bool UseCuda, AvxLevel AvxLevel, bool AllowFallback, bool SkipCheck,
  198. string[] SearchDirectories)
  199. {
  200. /// <inheritdoc/>
  201. public override string ToString()
  202. {
  203. string avxLevelString = AvxLevel switch
  204. {
  205. AvxLevel.None => "NoAVX",
  206. AvxLevel.Avx => "AVX",
  207. AvxLevel.Avx2 => "AVX2",
  208. AvxLevel.Avx512 => "AVX512",
  209. _ => "Unknown"
  210. };
  211. string searchDirectoriesString = "{ " + string.Join(", ", SearchDirectories) + " }";
  212. return $"NativeLibraryConfig Description:\n" +
  213. $"- LibraryName: {Library}\n" +
  214. $"- Path: '{Path}'\n" +
  215. $"- PreferCuda: {UseCuda}\n" +
  216. $"- PreferredAvxLevel: {avxLevelString}\n" +
  217. $"- AllowFallback: {AllowFallback}\n" +
  218. $"- SkipCheck: {SkipCheck}\n" +
  219. $"- SearchDirectories and Priorities: {searchDirectoriesString}";
  220. }
  221. }
  222. }
  223. #endif
  224. public sealed partial class NativeLibraryConfig
  225. {
  226. /// <summary>
  227. /// Set configurations for all the native libraries, including LLama and LLava
  228. /// </summary>
  229. [Obsolete("Please use NativeLibraryConfig.All instead, or set configurations for NativeLibraryConfig.LLama and NativeLibraryConfig.LLavaShared respectively.")]
  230. public static NativeLibraryConfigContainer Instance { get; }
  231. /// <summary>
  232. /// Set configurations for all the native libraries, including LLama and LLava
  233. /// </summary>
  234. public static NativeLibraryConfigContainer All { get; }
  235. /// <summary>
  236. /// Configuration for LLama native library
  237. /// </summary>
  238. public static NativeLibraryConfig LLama { get; }
  239. /// <summary>
  240. /// Configuration for LLava native library
  241. /// </summary>
  242. public static NativeLibraryConfig LLavaShared { get; }
  243. /// <summary>
  244. /// A dictionary mapping from version to corresponding llama.cpp commit hash.
  245. /// The version should be formatted int `[major].[minor].[patch]`. But there's an exceptance that you can
  246. /// use `master` as a version to get the llama.cpp commit hash from the master branch.
  247. /// </summary>
  248. public static Dictionary<string, string> VersionMap { get; } = new Dictionary<string, string>()
  249. // This value should be changed when we're going to publish new release. (any better approach?)
  250. {
  251. {"master", "f7001c"}
  252. };
  253. /// <summary>
  254. /// The current version.
  255. /// </summary>
  256. public static readonly string CurrentVersion = "master"; // This should be changed before publishing new version. TODO: any better approach?
  257. static NativeLibraryConfig()
  258. {
  259. LLama = new(NativeLibraryName.Llama);
  260. LLavaShared = new(NativeLibraryName.LlavaShared);
  261. All = new(LLama, LLavaShared);
  262. Instance = All;
  263. }
  264. #if NETSTANDARD2_0
  265. private NativeLibraryConfig(NativeLibraryName nativeLibraryName)
  266. {
  267. NativeLibraryName = nativeLibraryName;
  268. }
  269. #endif
  270. /// <summary>
  271. /// Check if the native library has already been loaded. Configuration cannot be modified if this is true.
  272. /// </summary>
  273. public bool LibraryHasLoaded { get; internal set; }
  274. internal NativeLibraryName NativeLibraryName { get; }
  275. internal NativeLogConfig.LLamaLogCallback? LogCallback { get; private set; } = null;
  276. private void ThrowIfLoaded()
  277. {
  278. if (LibraryHasLoaded)
  279. throw new InvalidOperationException("The library has already loaded, you can't change the configurations. " +
  280. "Please finish the configuration setting before any call to LLamaSharp native APIs." +
  281. "Please use NativeLibraryConfig.DryRun if you want to see whether it's loaded " +
  282. "successfully but still have chance to modify the configurations.");
  283. }
  284. /// <summary>
  285. /// Set the log callback that will be used for all llama.cpp log messages
  286. /// </summary>
  287. /// <param name="callback"></param>
  288. /// <exception cref="NotImplementedException"></exception>
  289. public NativeLibraryConfig WithLogCallback(NativeLogConfig.LLamaLogCallback? callback)
  290. {
  291. ThrowIfLoaded();
  292. LogCallback = callback;
  293. return this;
  294. }
  295. /// <summary>
  296. /// Set the log callback that will be used for all llama.cpp log messages
  297. /// </summary>
  298. /// <param name="logger"></param>
  299. /// <exception cref="NotImplementedException"></exception>
  300. public NativeLibraryConfig WithLogCallback(ILogger? logger)
  301. {
  302. ThrowIfLoaded();
  303. // Redirect to llama_log_set. This will wrap the logger in a delegate and bind that as the log callback instead.
  304. NativeLogConfig.llama_log_set(logger);
  305. return this;
  306. }
  307. /// <summary>
  308. /// Try to load the native library with the current configurations,
  309. /// but do not actually set it to <see cref="NativeApi"/>.
  310. ///
  311. /// You can still modify the configuration after this calling but only before any call from <see cref="NativeApi"/>.
  312. /// </summary>
  313. /// <returns>Whether the running is successful.</returns>
  314. public bool DryRun()
  315. {
  316. LogCallback?.Invoke(LLamaLogLevel.Debug, $"Beginning dry run for {this.NativeLibraryName.GetLibraryName()}...");
  317. return NativeLibraryUtils.TryLoadLibrary(this) != IntPtr.Zero;
  318. }
  319. }
  320. /// <summary>
  321. /// A class to set same configurations to multiple libraries at the same time.
  322. /// </summary>
  323. public sealed class NativeLibraryConfigContainer
  324. {
  325. private NativeLibraryConfig[] _configs;
  326. /// <summary>
  327. /// All the configurations in this container.
  328. /// Please avoid calling this property explicitly, use <see cref="NativeLibraryConfig.LLama"/>
  329. /// and <see cref="NativeLibraryConfig.LLavaShared"/> instead.
  330. /// </summary>
  331. public NativeLibraryConfig[] Configs => _configs;
  332. internal NativeLibraryConfigContainer(params NativeLibraryConfig[] configs)
  333. {
  334. _configs = configs;
  335. }
  336. #region configurators
  337. #if NET6_0_OR_GREATER
  338. /// <summary>
  339. /// Load a specified native library as backend for LLamaSharp.
  340. /// When this method is called, all the other configurations will be ignored.
  341. /// </summary>
  342. /// <param name="llamaPath">The full path to the llama library to load.</param>
  343. /// <param name="llavaPath">The full path to the llava library to load.</param>
  344. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  345. public NativeLibraryConfigContainer WithLibrary(string? llamaPath, string? llavaPath)
  346. {
  347. foreach(var config in _configs)
  348. {
  349. if(config.NativeLibraryName == NativeLibraryName.Llama && llamaPath is not null)
  350. {
  351. config.WithLibrary(llamaPath);
  352. }
  353. if(config.NativeLibraryName == NativeLibraryName.LlavaShared && llavaPath is not null)
  354. {
  355. config.WithLibrary(llavaPath);
  356. }
  357. }
  358. return this;
  359. }
  360. /// <summary>
  361. /// Configure whether to use cuda backend if possible.
  362. /// </summary>
  363. /// <param name="enable"></param>
  364. /// <returns></returns>
  365. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  366. public NativeLibraryConfigContainer WithCuda(bool enable = true)
  367. {
  368. foreach(var config in _configs)
  369. {
  370. config.WithCuda(enable);
  371. }
  372. return this;
  373. }
  374. /// <summary>
  375. /// Configure the prefferred avx support level of the backend.
  376. /// </summary>
  377. /// <param name="level"></param>
  378. /// <returns></returns>
  379. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  380. public NativeLibraryConfigContainer WithAvx(AvxLevel level)
  381. {
  382. foreach (var config in _configs)
  383. {
  384. config.WithAvx(level);
  385. }
  386. return this;
  387. }
  388. /// <summary>
  389. /// Configure whether to allow fallback when there's no match for preferred settings.
  390. /// </summary>
  391. /// <param name="enable"></param>
  392. /// <returns></returns>
  393. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  394. public NativeLibraryConfigContainer WithAutoFallback(bool enable = true)
  395. {
  396. foreach (var config in _configs)
  397. {
  398. config.WithAutoFallback(enable);
  399. }
  400. return this;
  401. }
  402. /// <summary>
  403. /// Whether to skip the check when you don't allow fallback. This option
  404. /// may be useful under some complex conditions. For example, you're sure
  405. /// you have your cublas configured but LLamaSharp take it as invalid by mistake.
  406. /// </summary>
  407. /// <param name="enable"></param>
  408. /// <returns></returns>
  409. /// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
  410. public NativeLibraryConfigContainer SkipCheck(bool enable = true)
  411. {
  412. foreach (var config in _configs)
  413. {
  414. config.SkipCheck(enable);
  415. }
  416. return this;
  417. }
  418. /// <summary>
  419. /// Add self-defined search directories. Note that the file stucture of the added
  420. /// directories must be the same as the default directory. Besides, the directory
  421. /// won't be used recursively.
  422. /// </summary>
  423. /// <param name="directories"></param>
  424. /// <returns></returns>
  425. public NativeLibraryConfigContainer WithSearchDirectories(IEnumerable<string> directories)
  426. {
  427. foreach (var config in _configs)
  428. {
  429. config.WithSearchDirectories(directories);
  430. }
  431. return this;
  432. }
  433. /// <summary>
  434. /// Add self-defined search directories. Note that the file stucture of the added
  435. /// directories must be the same as the default directory. Besides, the directory
  436. /// won't be used recursively.
  437. /// </summary>
  438. /// <param name="directory"></param>
  439. /// <returns></returns>
  440. public NativeLibraryConfigContainer WithSearchDirectory(string directory)
  441. {
  442. foreach (var config in _configs)
  443. {
  444. config.WithSearchDirectory(directory);
  445. }
  446. return this;
  447. }
  448. /// <summary>
  449. /// Set the policy which decides how to select the desired native libraries and order them by priority.
  450. /// By default we use <see cref="DefaultNativeLibrarySelectingPolicy"/>.
  451. /// </summary>
  452. /// <param name="policy"></param>
  453. /// <returns></returns>
  454. public NativeLibraryConfigContainer WithSelectingPolicy(INativeLibrarySelectingPolicy policy)
  455. {
  456. foreach (var config in _configs)
  457. {
  458. config.WithSelectingPolicy(policy);
  459. }
  460. return this;
  461. }
  462. #endif
  463. /// <summary>
  464. /// Set the log callback that will be used for all llama.cpp log messages
  465. /// </summary>
  466. /// <param name="callback"></param>
  467. /// <exception cref="NotImplementedException"></exception>
  468. public NativeLibraryConfigContainer WithLogCallback(NativeLogConfig.LLamaLogCallback? callback)
  469. {
  470. foreach (var config in _configs)
  471. {
  472. config.WithLogCallback(callback);
  473. }
  474. return this;
  475. }
  476. /// <summary>
  477. /// Set the log callback that will be used for all llama.cpp log messages
  478. /// </summary>
  479. /// <param name="logger"></param>
  480. /// <exception cref="NotImplementedException"></exception>
  481. public NativeLibraryConfigContainer WithLogCallback(ILogger? logger)
  482. {
  483. foreach (var config in _configs)
  484. {
  485. config.WithLogCallback(logger);
  486. }
  487. return this;
  488. }
  489. #endregion
  490. /// <summary>
  491. /// Try to load the native library with the current configurations,
  492. /// but do not actually set it to <see cref="NativeApi"/>.
  493. ///
  494. /// You can still modify the configuration after this calling but only before any call from <see cref="NativeApi"/>.
  495. /// </summary>
  496. /// <returns>Whether the running is successful.</returns>
  497. public bool DryRun()
  498. {
  499. return _configs.All(config => config.DryRun());
  500. }
  501. }
  502. /// <summary>
  503. /// The name of the native library
  504. /// </summary>
  505. public enum NativeLibraryName
  506. {
  507. /// <summary>
  508. /// The native library compiled from llama.cpp.
  509. /// </summary>
  510. Llama,
  511. /// <summary>
  512. /// The native library compiled from the LLaVA example of llama.cpp.
  513. /// </summary>
  514. LlavaShared
  515. }
  516. internal static class LibraryNameExtensions
  517. {
  518. public static string GetLibraryName(this NativeLibraryName name)
  519. {
  520. switch (name)
  521. {
  522. case NativeLibraryName.Llama:
  523. return NativeApi.libraryName;
  524. case NativeLibraryName.LlavaShared:
  525. return NativeApi.llavaLibraryName;
  526. default:
  527. throw new ArgumentOutOfRangeException(nameof(name), name, null);
  528. }
  529. }
  530. }
  531. }