Skip to content
15 changes: 12 additions & 3 deletions LLama.Examples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,20 @@ __ __ ____ __

""");

// Configure native library to use
// Configure native library to use. This must be done before any other llama.cpp methods are called!
NativeLibraryConfig
.Instance
.WithCuda()
.WithLogs(LLamaLogLevel.Info);
.WithCuda();

// Configure logging. Change this to `true` to see log messages from llama.cpp
var showLLamaCppLogs = false;
NativeLibraryConfig
.Instance
.WithLogCallback((level, message) =>
{
if (showLLamaCppLogs)
Console.WriteLine($"[llama {level}]: {message.TrimEnd('\n')}");
});

// Calling this method forces loading to occur now.
NativeApi.llama_empty_call();
Expand Down
2 changes: 2 additions & 0 deletions LLama/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
[assembly: SuppressMessage("Interoperability", "CA1401:P/Invokes should not be visible", Justification = "LLamaSharp intentionally exports the native llama.cpp API")]

[assembly: SuppressMessage("Style", "IDE0070:Use 'System.HashCode'", Justification = "Not compatible with netstandard2.0")]

[assembly: SuppressMessage("Interoperability", "SYSLIB1054:Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time", Justification = "Not compatible with netstandard2.0")]
28 changes: 23 additions & 5 deletions LLama/Native/LLamaLogLevel.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace LLama.Native
{
/// <summary>
/// Severity level of a log message
/// </summary>
using System;
using Microsoft.Extensions.Logging;

namespace LLama.Native
{
/// <summary>
/// Severity level of a log message
/// </summary>
public enum LLamaLogLevel
{
/// <summary>
Expand All @@ -25,4 +28,19 @@ public enum LLamaLogLevel
/// </summary>
Debug = 5,
}

internal static class LLamaLogLevelExtensions
{
public static LogLevel ToLogLevel(this LLamaLogLevel llama)
{
return (llama) switch
{
LLamaLogLevel.Error => LogLevel.Error,
LLamaLogLevel.Warning => LogLevel.Warning,
LLamaLogLevel.Info => LogLevel.Information,
LLamaLogLevel.Debug => LogLevel.Debug,
_ => throw new ArgumentOutOfRangeException(nameof(llama), llama, null)
};
}
}
}
55 changes: 10 additions & 45 deletions LLama/Native/NativeApi.Load.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ static NativeApi()
// which llama.dll is used.
SetDllImportResolver();

// Set flag to indicate that this point has been passed. No native library config can be done after this point.
NativeLibraryConfig.LibraryHasLoaded = true;

// Immediately make a call which requires loading the llama DLL. This method call
// can't fail unless the DLL hasn't been loaded.
try
Expand All @@ -34,6 +37,10 @@ static NativeApi()
"to specify it at the very beginning of your code. For more informations about compilation, please refer to LLamaSharp repo on github.\n");
}

// Now that the "loaded" flag is set configure logging in llama.cpp
if (NativeLibraryConfig.Instance.LogCallback != null)
NativeLogConfig.llama_log_set(NativeLibraryConfig.Instance.LogCallback);

// Init llama.cpp backend
llama_backend_init();
}
Expand Down Expand Up @@ -80,47 +87,10 @@ private static void SetDllImportResolver()

private static void Log(string message, LLamaLogLevel level)
{
if (!enableLogging)
return;

if ((int)level > (int)logLevel)
return;
if (!message.EndsWith("\n"))
message += "\n";

var fg = Console.ForegroundColor;
var bg = Console.BackgroundColor;
try
{
ConsoleColor color;
string levelPrefix;
if (level == LLamaLogLevel.Debug)
{
color = ConsoleColor.Cyan;
levelPrefix = "[Debug]";
}
else if (level == LLamaLogLevel.Info)
{
color = ConsoleColor.Green;
levelPrefix = "[Info]";
}
else if (level == LLamaLogLevel.Error)
{
color = ConsoleColor.Red;
levelPrefix = "[Error]";
}
else
{
color = ConsoleColor.Yellow;
levelPrefix = "[UNK]";
}

Console.ForegroundColor = color;
Console.WriteLine($"{loggingPrefix} {levelPrefix} {message}");
}
finally
{
Console.ForegroundColor = fg;
Console.BackgroundColor = bg;
}
NativeLibraryConfig.Instance.LogCallback?.Invoke(level, message);
}

#region CUDA version
Expand Down Expand Up @@ -362,8 +332,6 @@ private static IntPtr TryLoadLibraries(LibraryName lib)
{
#if NET6_0_OR_GREATER
var configuration = NativeLibraryConfig.CheckAndGatherDescription(lib);
enableLogging = configuration.Logging;
logLevel = configuration.LogLevel;

// Set the flag to ensure the NativeLibraryConfig can no longer be modified
NativeLibraryConfig.LibraryHasLoaded = true;
Expand Down Expand Up @@ -455,8 +423,5 @@ string TryFindPath(string filename)
internal const string libraryName = "llama";
internal const string llavaLibraryName = "llava_shared";
private const string cudaVersionFile = "version.json";
private const string loggingPrefix = "[LLamaSharp Native]";
private static bool enableLogging = false;
private static LLamaLogLevel logLevel = LLamaLogLevel.Info;
}
}
14 changes: 5 additions & 9 deletions LLama/Native/NativeApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@

namespace LLama.Native
{
/// <summary>
/// Callback from llama.cpp with log messages
/// </summary>
/// <param name="level"></param>
/// <param name="message"></param>
public delegate void LLamaLogCallback(LLamaLogLevel level, string message);

/// <summary>
/// Direct translation of the llama.cpp API
/// </summary>
Expand Down Expand Up @@ -364,8 +357,11 @@ public static int llama_token_to_piece(SafeLlamaModelHandle model, LLamaToken ll
/// Register a callback to receive llama log messages
/// </summary>
/// <param name="logCallback"></param>
[DllImport(libraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern void llama_log_set(LLamaLogCallback logCallback);
[Obsolete("Use `NativeLogConfig.llama_log_set` instead")]
public static void llama_log_set(NativeLogConfig.LLamaLogCallback logCallback)
{
NativeLogConfig.llama_log_set(logCallback);
}

/// <summary>
/// Clear the KV cache
Expand Down
105 changes: 52 additions & 53 deletions LLama/Native/NativeLibraryConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;

namespace LLama.Native
{
Expand All @@ -9,39 +10,21 @@ namespace LLama.Native
/// Allows configuration of the native llama.cpp libraries to load and use.
/// All configuration must be done before using **any** other LLamaSharp methods!
/// </summary>
public sealed class NativeLibraryConfig
public sealed partial class NativeLibraryConfig
{
/// <summary>
/// Get the config instance
/// </summary>
public static NativeLibraryConfig Instance { get; } = new();

/// <summary>
/// Check if the native library has already been loaded. Configuration cannot be modified if this is true.
/// </summary>
public static bool LibraryHasLoaded { get; internal set; } = false;

private string? _libraryPath;
private string? _libraryPathLLava;

private bool _useCuda = true;
private AvxLevel _avxLevel;
private bool _allowFallback = true;
private bool _skipCheck = false;
private bool _logging = false;
private LLamaLogLevel _logLevel = LLamaLogLevel.Info;

/// <summary>
/// search directory -> priority level, 0 is the lowest.
/// </summary>
private readonly List<string> _searchDirectories = new List<string>();

private static void ThrowIfLoaded()
{
if (LibraryHasLoaded)
throw new InvalidOperationException("NativeLibraryConfig must be configured before using **any** other LLamaSharp methods!");
}

#region configurators
/// <summary>
/// Load a specified native library as backend for LLamaSharp.
Expand Down Expand Up @@ -117,35 +100,6 @@ public NativeLibraryConfig SkipCheck(bool enable = true)
return this;
}

/// <summary>
/// Whether to output the logs to console when loading the native library with your configuration.
/// </summary>
/// <param name="enable"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
public NativeLibraryConfig WithLogs(bool enable)
{
ThrowIfLoaded();

_logging = enable;
return this;
}

/// <summary>
/// Enable console logging with the specified log logLevel.
/// </summary>
/// <param name="logLevel"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">Thrown if `LibraryHasLoaded` is true.</exception>
public NativeLibraryConfig WithLogs(LLamaLogLevel logLevel = LLamaLogLevel.Info)
{
ThrowIfLoaded();

_logging = true;
_logLevel = logLevel;
return this;
}

/// <summary>
/// Add self-defined search directories. Note that the file stucture of the added
/// directories must be the same as the default directory. Besides, the directory
Expand Down Expand Up @@ -196,8 +150,6 @@ internal static Description CheckAndGatherDescription(LibraryName library)
Instance._avxLevel,
Instance._allowFallback,
Instance._skipCheck,
Instance._logging,
Instance._logLevel,
Instance._searchDirectories.Concat(new[] { "./" }).ToArray()
);
}
Expand Down Expand Up @@ -279,7 +231,7 @@ public enum AvxLevel
Avx512,
}

internal record Description(string? Path, LibraryName Library, bool UseCuda, AvxLevel AvxLevel, bool AllowFallback, bool SkipCheck, bool Logging, LLamaLogLevel LogLevel, string[] SearchDirectories)
internal record Description(string? Path, LibraryName Library, bool UseCuda, AvxLevel AvxLevel, bool AllowFallback, bool SkipCheck, string[] SearchDirectories)
{
public override string ToString()
{
Expand All @@ -301,14 +253,61 @@ public override string ToString()
$"- PreferredAvxLevel: {avxLevelString}\n" +
$"- AllowFallback: {AllowFallback}\n" +
$"- SkipCheck: {SkipCheck}\n" +
$"- Logging: {Logging}\n" +
$"- LogLevel: {LogLevel}\n" +
$"- SearchDirectories and Priorities: {searchDirectoriesString}";
}
}
}
#endif

public sealed partial class NativeLibraryConfig
{
/// <summary>
/// Get the config instance
/// </summary>
public static NativeLibraryConfig Instance { get; } = new();

/// <summary>
/// Check if the native library has already been loaded. Configuration cannot be modified if this is true.
/// </summary>
public static bool LibraryHasLoaded { get; internal set; }

internal NativeLogConfig.LLamaLogCallback? LogCallback;

private static void ThrowIfLoaded()
{
if (LibraryHasLoaded)
throw new InvalidOperationException("NativeLibraryConfig must be configured before using **any** other LLamaSharp methods!");
}

/// <summary>
/// Set the log callback that will be used for all llama.cpp log messages
/// </summary>
/// <param name="callback"></param>
/// <exception cref="NotImplementedException"></exception>
public NativeLibraryConfig WithLogCallback(NativeLogConfig.LLamaLogCallback? callback)
{
ThrowIfLoaded();

LogCallback = callback;
return this;
}

/// <summary>
/// Set the log callback that will be used for all llama.cpp log messages
/// </summary>
/// <param name="logger"></param>
/// <exception cref="NotImplementedException"></exception>
public NativeLibraryConfig WithLogCallback(ILogger? logger)
{
ThrowIfLoaded();

// Redirect to llama_log_set. This will wrap the logger in a delegate and bind that as the log callback instead.
NativeLogConfig.llama_log_set(logger);

return this;
}
}

internal enum LibraryName
{
Llama,
Expand Down
Loading