Browse Source

feat: add the framework and basic usages.

tags/v0.2.1
Yaohui Liu 3 years ago
commit
5a79edeb51
No known key found for this signature in database GPG Key ID: E86D01E1809BD23E
24 changed files with 2437 additions and 0 deletions
  1. +341
    -0
      .gitignore
  2. +15
    -0
      LLama.Console/LLama.Console.csproj
  3. +16
    -0
      LLama.Console/Program.cs
  4. +15
    -0
      LLama.Unittest/BasicTest.cs
  5. +31
    -0
      LLama.Unittest/LLama.Unittest.csproj
  6. +1
    -0
      LLama.Unittest/Usings.cs
  7. +19
    -0
      LLama/Exceptions/RuntimeError.cs
  8. +99
    -0
      LLama/LLamaCache.cs
  9. +925
    -0
      LLama/LLamaModel.cs
  10. +23
    -0
      LLama/LLamaSharp.csproj
  11. +10
    -0
      LLama/LLamaState.cs
  12. +34
    -0
      LLama/LLamaTypes.cs
  13. +70
    -0
      LLama/Logger.cs
  14. +65
    -0
      LLama/Native/LLamaContextParams.cs
  15. +20
    -0
      LLama/Native/LLamaFtype.cs
  16. +31
    -0
      LLama/Native/LLamaTokenData.cs
  17. +32
    -0
      LLama/Native/LLamaTokenDataArray.cs
  18. +128
    -0
      LLama/Native/NativeApi.Sampling.cs
  19. +226
    -0
      LLama/Native/NativeApi.cs
  20. +15
    -0
      LLama/Native/NativeInfo.cs
  21. +26
    -0
      LLama/Native/SafeLLamaContextHandle.cs
  22. +32
    -0
      LLama/Native/SafeLLamaHandleBase.cs
  23. +212
    -0
      LLama/Native/SamplingApi.cs
  24. +51
    -0
      LLamaSharp.sln

+ 341
- 0
.gitignore View File

@@ -0,0 +1,341 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

# Visual Studio 2017 auto generated files
Generated\ Files/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUNIT
*.VisualState.xml
TestResult.xml

# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c

# Benchmark Results
BenchmarkDotNet.Artifacts/

# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json

# StyleCop
StyleCopReport.xml

# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*.ilk
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc

# Chutzpah Test files
_Chutzpah*

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap

# Visual Studio Trace Files
*.e2e

# TFS 2012 Local Workspace
$tf/

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user

# JustCode is a .NET coding add-in
.JustCode

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json

# Visual Studio code coverage results
*.coverage
*.coveragexml

# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*

# MightyMoose
*.mm.*
AutoTest.Net/

# Web workbench (sass)
.sass-cache/

# Installshield output folder
[Ee]xpress/

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets

# Microsoft Azure Build Output
csx/
*.build.csdef

# Microsoft Azure Emulator
ecf/
rcf/

# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs

# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak

# SQL Server files
*.mdf
*.ldf
*.ndf

# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser

# Microsoft Fakes
FakesAssemblies/

# GhostDoc plugin setting file
*.GhostDoc.xml

# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/

# Visual Studio 6 build log
*.plg

# Visual Studio 6 workspace options file
*.opt

# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw

# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# JetBrains Rider
.idea/
*.sln.iml

# CodeRush
.cr/

# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc

# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config

# Tabs Studio
*.tss

# Telerik's JustMock configuration file
*.jmconfig

# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs

# OpenCover UI analysis results
OpenCover/

# Azure Stream Analytics local run output
ASALocalRun/

# MSBuild Binary and Structured Log
*.binlog

# NVidia Nsight GPU debugger configuration file
*.nvuser

# MFractors (Xamarin productivity tool) working folder
.mfractor/
/docs/build
src/TensorFlowNET.Native/bazel-*
src/TensorFlowNET.Native/c_api.h
/.vscode
test/TensorFlowNET.Examples/mnist


# training model resources
.resources
/redist
*.xml
*.xsd

+ 15
- 0
LLama.Console/LLama.Console.csproj View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\LLama\LLamaSharp.csproj" />
</ItemGroup>

</Project>

+ 16
- 0
LLama.Console/Program.cs View File

@@ -0,0 +1,16 @@
using LLama;
using LLama.Types;

string modelPath = @"D:\development\llama\weights\LLaMA\7B\ggml-model-q4_0.bin";
LLamaModel model = new(modelPath, logits_all: false, verbose: false);
List<ChatCompletionMessage> chats = new List<ChatCompletionMessage>();
chats.Add(new ChatCompletionMessage("user", "Hi, Alice, I'm Rinne.", null));
chats.Add(new ChatCompletionMessage("assistant", "Hi, Rinne, I'm Alice. What can I do for you?", null));
while (true)
{
Console.Write("You: ");
var question = Console.ReadLine();
chats.Add(new ChatCompletionMessage("user", question, null));
var output = model.CreateChatCompletion(chats, max_tokens: 256);
Console.WriteLine($"LLama AI: {output.Choices[0].Message.Content}");
}

+ 15
- 0
LLama.Unittest/BasicTest.cs View File

@@ -0,0 +1,15 @@
namespace LLama.Unittest
{
public class BasicTest
{
[Fact]
public void SimpleQA()
{
string modelPath = @"D:\development\llama\weights\LLaMA\7B\ggml-model-f32.bin";
LLamaModel model = new(modelPath, logits_all: false);
var output = model.Call("Q: Why God makes many people believe him? A: ", max_tokens: 64, stop: new[] { "Q:", "\n" },
echo: true);
Console.WriteLine(output);
}
}
}

+ 31
- 0
LLama.Unittest/LLama.Unittest.csproj View File

@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>LLama.Unittest</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Platforms>AnyCPU;x64</Platforms>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\llama-sharp\LLamaSharp.csproj" />
<ProjectReference Include="..\LLama\LLamaSharp.csproj" />
</ItemGroup>

</Project>

+ 1
- 0
LLama.Unittest/Usings.cs View File

@@ -0,0 +1 @@
global using Xunit;

+ 19
- 0
LLama/Exceptions/RuntimeError.cs View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace LLama.Exceptions
{
public class RuntimeError: Exception
{
public RuntimeError()
{

}

public RuntimeError(string message): base(message)
{

}
}
}

+ 99
- 0
LLama/LLamaCache.cs View File

@@ -0,0 +1,99 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LLama
{
using llama_token = Int32;
/// <summary>
/// Cache for a llama.cpp model.
/// </summary>
public class LLamaCache
{
private Dictionary<llama_token[], LinkedListNode<KeyValuePair<llama_token[], LLamaState>>> _cacheState;
private LinkedList<KeyValuePair<llama_token[], LLamaState>> _cacheList;
private int _capacity;

public int CacheSize
{
get
{
return _cacheState.Values.Select(s => s.Value.Value.Size).Sum();
}
}

/// <summary>
///
/// </summary>
/// <param name="capacity">The max capacity (bytes).</param>
public LLamaCache(int capacity = 2 << 30)
{
_cacheState = new();
_cacheList = new();
_capacity = capacity;
}

public LLamaState this[llama_token[] key]
{
get
{
var prefixKey = FindLongestPrefixKey(key);
if(prefixKey is null)
{
throw new KeyNotFoundException();
}
var value = _cacheState[prefixKey];
MoveNodeToEnd(prefixKey);
return value.Value.Value;
}
set
{
var node = _cacheList.AddLast(new KeyValuePair<llama_token[], LLamaState>(key, value));
_cacheState[key] = node;
while(CacheSize > _capacity && _cacheList.Count > 0)
{
var topop = _cacheList.First;
_cacheState.Remove(topop.Value.Key);
_cacheList.RemoveFirst();
}
}
}

public bool Contains(llama_token[] key)
{
return FindLongestPrefixKey(key) is not null;
}

private llama_token[]? FindLongestPrefixKey(llama_token[] key)
{
int minLen = 0;
llama_token[]? minKey = null;
var keys = _cacheState.Keys.Select(k => (k, LLamaModel.LongestTokenPrefix(k, key)));
foreach(var (k, prefixLen) in keys)
{
if(prefixLen > minLen)
{
minLen = prefixLen;
minKey = k;
}
}
return minKey;
}

private void MoveNodeToEnd(llama_token[] key)
{
if (!_cacheState.TryGetValue(key, out var node))
{
return;
}

_cacheState.Remove(key);
_cacheList.Remove(node);

var newNode = _cacheList.AddLast(new KeyValuePair<llama_token[], LLamaState>(key, node.Value.Value));
_cacheState.Add(key, newNode);
}
}
}

+ 925
- 0
LLama/LLamaModel.cs View File

@@ -0,0 +1,925 @@
using LLama.Exceptions;
using LLama.Native;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using LLama.Types;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

namespace LLama
{
using llama_token = Int32;
/// <summary>
/// High-level Wrapper of a llama.cpp model for inference.
/// </summary>
public class LLamaModel
{
private string _model_path;
LLamaContextParams _params;
private int _n_threads;
private int _n_batch;
private int _last_n_tokens_size;
private string? _lora_base;
private string? _lora_path;
private bool _verbose;

private Queue<llama_token> _eval_tokens;
private Queue<float[]> _eval_logits;
private LLamaCache? _cache;
private SafeLLamaContextHandle _ctx;

private static readonly (int, int)[] _numAndPatterns = new (int, int)[] { (2, 192), (3, 224), (4, 240) };

/// <summary>
/// Load a llama.cpp model from the path.
/// </summary>
/// <remarks>Note that the API is still unstable. The order of them is likely to
/// be changed in the future. It's recommened to specify the parameter name when
/// building your app. We use the cpp style parameter names here because it introduces
/// convenience for searching the docs.</remarks>
/// <param name="model_path">Path to the model.</param>
/// <param name="n_ctx">Maximum context size.</param>
/// <param name="n_parts">Number of parts to split the model into. If -1, the number of parts is automatically determined.</param>
/// <param name="seed">Random seed. 0 for random.</param>
/// <param name="f16_kv">Use half-precision for key/value cache.</param>
/// <param name="logits_all">Return logits for all tokens, not just the last token.</param>
/// <param name="vocab_only">Only load the vocabulary no weights.</param>
/// <param name="use_mmap">Use mmap if possible.</param>
/// <param name="use_mlock">Force the system to keep the model in RAM.</param>
/// <param name="embedding">Embedding mode only.</param>
/// <param name="n_threads">Number of threads to use. If is not specified, the number of threads is automatically determined.</param>
/// <param name="n_batch">Maximum number of prompt tokens to batch together when calling llama_eval.</param>
/// <param name="last_n_tokens_size">Maximum number of tokens to keep in the last_n_tokens deque.</param>
/// <param name="lora_base">Optional path to base model, useful if using a quantized base model and you want to apply LoRA to an f16 model.</param>
/// <param name="lora_path">Path to a LoRA file to apply to the model.</param>
/// <param name="verbose">Print verbose output to stderr.</param>
public LLamaModel(string model_path, int n_ctx = 512, int n_parts = -1, int seed = 1337,
bool f16_kv = true, bool logits_all = false, bool vocab_only = false, bool use_mmap = true,
bool use_mlock = false, bool embedding = false, int n_threads = -1, int n_batch = 512,
int last_n_tokens_size = 64, string? lora_base = null, string? lora_path = null, bool verbose = true)
{
_verbose = verbose;
_model_path = model_path;

_params = NativeApi.llama_context_default_params();
_params.n_ctx = n_ctx;
_params.n_parts = n_parts;
_params.seed = seed;
_params.f16_kv = f16_kv;
_params.logits_all = logits_all;
_params.vocab_only = vocab_only;
_params.use_mmap = lora_path is null ? use_mmap : false;
_params.use_mlock = use_mlock;
_params.embedding = embedding;

_last_n_tokens_size = last_n_tokens_size;
_n_batch = Math.Min(n_ctx, n_batch);

_eval_tokens = new Queue<int>(capacity: n_ctx);
_eval_logits = new Queue<float[]>(logits_all ? n_ctx : 1);

_cache = null;

_n_threads = n_threads;
if(_n_threads == -1)
{
_n_threads = Math.Max(Environment.ProcessorCount / 2, 1);
}

_lora_base = lora_base;
_lora_path = lora_path;

if(!File.Exists(model_path) && !Directory.Exists(model_path))
{
throw new FileNotFoundException($"Model path does not exist: {model_path}");
}

// Move from heap to stack to prevent the moving.
_ctx = new SafeLLamaContextHandle(NativeApi.llama_init_from_file(Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(model_path)), _params));

Debug.Assert(_ctx.DangerousGetHandle() != IntPtr.Zero);

if(_lora_path is not null)
{
if(NativeApi.llama_apply_lora_from_file(_ctx, lora_path, lora_base, _n_threads) != 0)
{
throw new RuntimeError($"Failed to apply LoRA from lora path: {_lora_path} to base path: {_lora_base}");
}
}

if (_verbose)
{
#if NET6_0_OR_GREATER
Logger.Default.Info(Marshal.PtrToStringUTF8(NativeApi.llama_print_system_info()));
#endif
}
}

public LLamaModel(LLamaModel other)
{
_ctx = other._ctx;
_model_path = other._model_path;
_params = other._params;
_last_n_tokens_size = other._last_n_tokens_size;
_n_threads = other._n_threads;
_n_batch = other._n_batch;
_verbose = other._verbose;
_lora_base = other._lora_base;
_lora_path = other._lora_path;
_eval_logits = new Queue<float[]>(other._eval_logits);
_eval_tokens = new Queue<llama_token>(other._eval_tokens);
}

/// <summary>
/// Tokenize a string.
/// </summary>
/// <param name="text">The utf-8 encoded string to tokenize.</param>
/// <returns>A list of tokens.</returns>
/// <exception cref="RuntimeError">If the tokenization failed.</exception>
public List<llama_token> Tokenize(string text)
{
Debug.Assert(_ctx.DangerousGetHandle() != IntPtr.Zero);
var n_ctx = NativeApi.llama_n_ctx(_ctx);
var tokens = new llama_token[n_ctx];
var n_tokens = NativeApi.llama_tokenize(_ctx, text, tokens, n_ctx, true);
if(n_tokens < 0)
{
throw new RuntimeError($"Failed to tokenize: text=\"{text}\" n_tokens={n_tokens}");
}
return tokens.Take(n_tokens).ToList();
}

/// <summary>
/// Detokenize a list of tokens.
/// </summary>
/// <param name="tokens">The list of tokens to detokenize.</param>
/// <returns>The detokenized string.</returns>
public string DeTokenize(IEnumerable<llama_token> tokens)
{
Debug.Assert(_ctx.DangerousGetHandle() != IntPtr.Zero);
string output = "";
foreach(var token in tokens)
{
#if NET6_0_OR_GREATER
output += Marshal.PtrToStringUTF8(NativeApi.llama_token_to_str(_ctx, token));
#else
output += Marshal.PtrToStringAnsi(NativeApi.llama_token_to_str(_ctx, token));
#endif
}
return output;
}

/// <summary>
/// Set the cache.
/// </summary>
/// <param name="cache">The cache to set.</param>
public void SetCache(LLamaCache? cache)
{
_cache = cache;
}

/// <summary>
/// Reset the model state.
/// </summary>
public void Reset()
{
_eval_tokens.Clear();
_eval_logits.Clear();
}

/// <summary>
/// Evaluate a list of tokens.
/// </summary>
/// <param name="tokens">The list of tokens to evaluate.</param>
/// <exception cref="RuntimeError"></exception>
public unsafe void Eval(List<llama_token> tokens)
{
Debug.Assert(_ctx.DangerousGetHandle() != IntPtr.Zero);
var n_ctx = NativeApi.llama_n_ctx(_ctx);
for(int i = 0; i < tokens.Count; i += _n_batch)
{
var batch = tokens.Take(Math.Min(tokens.Count, i + _n_batch)).Skip(i);
llama_token n_past = Math.Min(n_ctx - batch.Count(), _eval_tokens.Count);
llama_token n_tokens = batch.Count();
llama_token return_code = NativeApi.llama_eval(
ctx: _ctx,
tokens: batch.ToArray(),
n_tokens: n_tokens,
n_past: n_past,
n_threads: _n_threads
);
if(return_code != 0)
{
throw new RuntimeError($"llama_eval returned {return_code}");
}
foreach(var b in batch)
{
_eval_tokens.Enqueue(b);
}
int rows = _params.logits_all ? n_tokens : 1;
llama_token n_vocab = NativeApi.llama_n_vocab(_ctx);
var cols = n_vocab;
var logits_view = NativeApi.llama_get_logits(_ctx);
for(int j = 0; j < rows; j++)
{
float[] logit = new float[cols];
for(int k = 0; k < cols; k++)
{
logit[k] = logits_view[j * cols + k];
}
_eval_logits.Enqueue(logit);
}
}
}

private llama_token SampleInternal(llama_token[] last_n_tokens_data, int last_n_tokens_size, int top_k,
float top_p, float temp, float repeat_penalty, float frequency_penalty, float presence_penalty)
{
Debug.Assert(_ctx.DangerousGetHandle() != IntPtr.Zero);
Debug.Assert(_eval_logits.Count > 0);
llama_token n_vocab = NativeApi.llama_n_vocab(_ctx);
var logits = _eval_logits.Last();
LLamaTokenData[] data = new LLamaTokenData[n_vocab];
for(int i = 0; i < n_vocab; i++)
{
data[i] = new LLamaTokenData(i, logits[i], .0f);
}
ulong size = (ulong)n_vocab;
bool sorted = false;
LLamaTokenDataArray candidates = new(data, size, sorted);
SamplingApi.llama_sample_repetition_penalty(_ctx, candidates, last_n_tokens_data, (ulong)last_n_tokens_size,
repeat_penalty);
//SamplingApi.llama_sample_frequency_and_presence_penalties(_ctx, candidates, last_n_tokens_data, (ulong)last_n_tokens_size,
// frequency_penalty, presence_penalty);
if(temp == .0f)
{
return SamplingApi.llama_sample_token_greedy(_ctx, candidates);
}
else
{
SamplingApi.llama_sample_top_k(_ctx, candidates, top_k, 1);
SamplingApi.llama_sample_tail_free(_ctx, candidates, 1.0f, 1);
SamplingApi.llama_sample_typical(_ctx, candidates, 1.0f, 1);
SamplingApi.llama_sample_top_p(_ctx, candidates, top_p, 1);
SamplingApi.llama_sample_temperature(_ctx, candidates, temp);
return SamplingApi.llama_sample_token(_ctx, candidates);
}
}

/// <summary>
/// Sample a token from the model.
/// </summary>
/// <param name="top_k">The top-k sampling parameter.</param>
/// <param name="top_p">The top-p sampling parameter.</param>
/// <param name="temp">The temperature parameter.</param>
/// <param name="repeat_penalty">The repeat penalty parameter.</param>
/// <param name="frequency_penalty"></param>
/// <param name="presence_penalty"></param>
/// <returns>The sampled token.</returns>
public llama_token Sample(int top_k, float top_p, float temp, float repeat_penalty, float frequency_penalty = .0f,
float presence_penalty = .0f)
{
Debug.Assert(_ctx.DangerousGetHandle() != IntPtr.Zero);
var last_n_tokens_data = Enumerable.Repeat(0, Math.Max(0, _last_n_tokens_size - _eval_tokens.Count));
last_n_tokens_data = last_n_tokens_data.Concat(_eval_tokens.ToList()
.Skip(Math.Max(0, _eval_tokens.Count - _last_n_tokens_size)));
llama_token[] tokens_data = new llama_token[_last_n_tokens_size];
int i = 0;
foreach(var data in last_n_tokens_data)
{
if(i < _last_n_tokens_size)
{
tokens_data[i++] = data;
}
else
{
break;
}
}
return SampleInternal(tokens_data, _last_n_tokens_size, top_k, top_p, temp, repeat_penalty, frequency_penalty, presence_penalty);
}

/// <summary>
/// Create a generator of tokens from a prompt.
/// </summary>
/// <example>
/// Examples:
/// var llama = new LlamaModel("models/ggml-7b.bin")
/// var tokens = llama.Tokenize(b"Hello, world!")
/// foreach(var token in llama.Generate(tokens, top_k:40, top_p:0.95, temp:1.0, repeat_penalty:1.1)){
/// Console.WriteLine(llama.DeTokenize(new []{token}));
/// }
/// </example>
/// <param name="tokens"></param>
/// <param name="top_k"></param>
/// <param name="top_p"></param>
/// <param name="temp"></param>
/// <param name="repeat_penalty"></param>
/// <param name="frequency_penalty"></param>
/// <param name="presence_penalty"></param>
/// <param name="reset"></param>
/// <returns></returns>
public IEnumerable<llama_token> Generate(IEnumerable<llama_token> tokens, int top_k, float top_p, float temp,
float repeat_penalty, float frequency_penalty = .0f, float presence_penalty = .0f, bool reset = true)
{
Debug.Assert(_ctx.DangerousGetHandle() != IntPtr.Zero);
if(reset && _eval_tokens.Count > 0)
{
int longest_prefix = 0;
foreach(var (a, b) in _eval_tokens.ToList().Zip(tokens.Take(tokens.Count() - 1), (x, y) => (x, y)))
{
if(a == b)
{
longest_prefix += 1;
}
else
{
break;
}
}
if(longest_prefix > 0)
{
if (_verbose)
{
Logger.Default.Info("Llama.generate: prefix-match hit");
}
reset = false;
tokens = tokens.Skip(longest_prefix);
for(int i = 0; i < _eval_tokens.Count - longest_prefix; i++)
{
_eval_tokens.Dequeue();
if(_eval_logits.Count > 0)
{
_eval_logits.Dequeue();
}
}
}
}

if (reset)
{
Reset();
}

while (true)
{
Eval(tokens.ToList());
var token = Sample(top_k, top_p, temp, frequency_penalty, presence_penalty, repeat_penalty);
yield return token;
// TODO(Rinne): verify if the implementation is correct.
}
}

/// <summary>
/// Embed a string.
/// </summary>
/// <param name="input">The utf-8 encoded string to embed.</param>
/// <returns>An embedding object.</returns>
/// <exception cref="RuntimeError"></exception>
public unsafe Embedding CreateEmbedding(string input)
{
Debug.Assert(_ctx.DangerousGetHandle() != IntPtr.Zero);
if (!_params.embedding)
{
throw new RuntimeError("Llama model must be created with embedding=True to call this method");
}

if (_verbose)
{
NativeApi.llama_reset_timings(_ctx);
}

var tokens = Tokenize(input);
Reset();
Eval(tokens);
int n_tokens = tokens.Count;
var embeddingPtr = NativeApi.llama_get_embeddings(_ctx);
int cnt = NativeApi.llama_n_embd(_ctx);
float[] embedding = new float[cnt];
for(int i = 0; i < cnt; i++)
{
embedding[i] = embeddingPtr[i];
}

if (_verbose)
{
NativeApi.llama_print_timings(_ctx);
}

return new Embedding("list", _model_path, new[] { new EmbeddingData(0, "embedding", embedding) },
new EmbeddingUsage(n_tokens, n_tokens));
}

public float[] Embed(string input)
{
return CreateEmbedding(input).Data[0].Embedding;
}

/// <summary>
///
/// </summary>
/// <param name="prompt"></param>
/// <param name="suffix"></param>
/// <param name="max_tokens"></param>
/// <param name="temperature"></param>
/// <param name="top_p"></param>
/// <param name="logprobs"></param>
/// <param name="echo"></param>
/// <param name="stop"></param>
/// <param name="frequency_penalty"></param>
/// <param name="presence_penalty"></param>
/// <param name="repeat_penalty"></param>
/// <param name="top_k"></param>
/// <param name="stream"></param>
/// <returns>IEnumerable of Completion and CompletionChunk</returns>
/// <exception cref="ArgumentException"></exception>
private IEnumerable<object> CreateCompletionInternal(string prompt, string?suffix = null, int max_tokens = 16, float temperature = 0.8f,
float top_p = 0.95f, int logprobs = -1, bool echo = false, string[]? stop = null, float frequency_penalty = .0f,
float presence_penalty = .0f, float repeat_penalty = 1.1f, int top_k = 40, bool stream = false)
{
Debug.Assert(_ctx.DangerousGetHandle() != IntPtr.Zero);
string completionId = $"cmpl-{Guid.NewGuid()}";
var created = DateTime.Now.Millisecond;
List<llama_token> completionTokens = new List<llama_token>();

var promptTokens = Tokenize($" {prompt}");
string text = "";
int returnedCharacters = 0;
if(stop is null)
{
stop = new string[0];
}

if (_verbose)
{
NativeApi.llama_reset_timings(_ctx);
}

if(promptTokens.Count + max_tokens > NativeApi.llama_n_ctx(_ctx))
{
throw new ArgumentException($"Requested tokens exceed context window of {NativeApi.llama_n_ctx(_ctx)}");
}
if(logprobs != -1 && !_params.logits_all)
{
throw new ArgumentException("logprobs is not supported for models created with logits_all=False");
}

if(_cache is not null)
{
try
{
// TODO(Rinne): revise it since it will compare reference instead of elements.
var cacheItem = _cache[promptTokens.ToArray()];
var cachePrefixLen = LongestTokenPrefix(_eval_tokens.AsEnumerable(), promptTokens);
var evalPrefixLen = LongestTokenPrefix(_eval_tokens.AsEnumerable(), promptTokens);
if(cachePrefixLen > evalPrefixLen)
{
LoadState(cacheItem);
if (_verbose)
{
Logger.Default.Info("Llama._create_completion: cache hit");
}
}
}
catch (KeyNotFoundException)
{
if (_verbose)
{
Logger.Default.Warn("Llama._create_completion: cache miss");
}
}
}

string finishReason = "length";
int multibyteFix = 0;
bool reset = true;
List<llama_token> tokens = new(promptTokens);
if (reset && _eval_tokens.Count > 0)
{
int longest_prefix = 0;
foreach (var (a, b) in _eval_tokens.ToList().Zip(tokens.Take(tokens.Count - 1), (x, y) => (x, y)))
{
if (a == b)
{
longest_prefix += 1;
}
else
{
break;
}
}
if (longest_prefix > 0)
{
if (_verbose)
{
Logger.Default.Info("Llama.generate: prefix-match hit");
}
reset = false;
tokens = tokens.Skip(longest_prefix).ToList();
for (int i = 0; i < _eval_tokens.Count - longest_prefix; i++)
{
_eval_tokens.Dequeue();
if (_eval_logits.Count > 0)
{
_eval_logits.Dequeue();
}
}
}
}

if (reset)
{
Reset();
}
//foreach (var token in Generate(promptTokens, top_k, top_p, temperature, frequency_penalty, presence_penalty, repeat_penalty))
while(true)
{
Eval(tokens);
var token = Sample(top_k, top_p, temperature, repeat_penalty, frequency_penalty, presence_penalty);
tokens.Clear();
tokens.Add(token);
if (token == NativeApi.llama_token_eos())
{
text = DeTokenize(completionTokens);
finishReason = "stop";
break;
}

completionTokens.Add(token);

string allText = DeTokenize(completionTokens);

int cut = Math.Min(3, allText.Length);
for(int i = allText.Length - cut; i < allText.Length; i++)
{
var c = (int)allText[i];
int k = cut - i;
foreach(var (num, pattern) in _numAndPatterns)
{
if(num > k && (pattern & c) == pattern)
{
multibyteFix = num - k;
}
}
}

if(multibyteFix > 0)
{
multibyteFix--;
continue;
}

var anyStop = stop.Where(s => allText.Contains(s));
if(anyStop.Count() > 0)
{
var firstStop = anyStop.First();
text = allText.Substring(0, allText.IndexOf(firstStop));
finishReason = "stop";
break;
}

if (stream)
{
var start = returnedCharacters;
int longest = 0;
foreach(var s in stop)
{
for(int i = s.Length; i > 0; i--)
{
if(allText.EndsWith(s.Substring(0, i)))
{
if(i > longest)
{
longest = i;
}
break;
}
}
}
text = allText.Substring(0, allText.Length - longest);
returnedCharacters += text.Skip(start).Count();
yield return new CompletionChunk(completionId, "text_completion", created, _model_path, new CompletionChoice[]
{
new CompletionChoice(text.Substring(returnedCharacters), 0, null, finishReason)
});
}
}

if(_cache is not null)
{
if (_verbose)
{
Logger.Default.Info("Llama._create_completion: cache save");
}
_cache[promptTokens.Concat(completionTokens).ToArray()] = SaveState();
}

if (stream)
{
yield return new CompletionChunk(completionId, "text_completion", created, _model_path, new CompletionChoice[]
{
new CompletionChoice(text.Substring(returnedCharacters), 0, null, finishReason)
});
}

string textStr = text;
if (echo)
{
textStr = prompt + textStr;
}
if(suffix is not null)
{
textStr = textStr + suffix;
}

CompletionLogprobs? logProbs = null;
if (logprobs != -1)
{
int textOffset = 0;
List<int> textOffsets = new();
List<float> tokenLogprobs = new();
List<string> tokenStrs = new();
List<Dictionary<string, float>> topLogprobs = new();

var allTokens = promptTokens.Concat(completionTokens).ToArray();
var allTokenStrs = allTokens.Select(t => DeTokenize(new[] { t }));
var allLogProbs = _eval_logits.Select(row => LogitsToLogprobs(row));

foreach (var (token, tokenStr, logProbsToken) in allTokens.Zip(allTokenStrs, (x, y) => (x, y))
.Zip(allLogProbs, (x, y) => (x.x, x.y, y)))
{
textOffsets.Add(textOffset);
textOffset += tokenStr.Length;
tokenStrs.Add(tokenStr);
var sortedLogprobs = logProbsToken.Zip(Enumerable.Range(0, logProbsToken.Count()), (x, y) => (x, y))
.OrderByDescending(x => x.x).ToList();
tokenLogprobs.Add(sortedLogprobs[token].x);
var topLogprob = sortedLogprobs.Take(logprobs).ToDictionary(t => DeTokenize(new[] { t.y }), t => t.x);
topLogprob[tokenStr] = sortedLogprobs[token].x;
topLogprobs.Add(topLogprob);
}

logProbs = new(textOffsets.ToArray(), tokenLogprobs.ToArray(), tokenStrs.ToArray(), topLogprobs.ToArray());
}

if (_verbose)
{
NativeApi.llama_print_timings(_ctx);
}

yield return new Completion(completionId, "text_completion", created, _model_path, new CompletionChoice[]
{
new CompletionChoice(text, 0, logProbs, finishReason)
}, new CompletionUsage(promptTokens.Count, completionTokens.Count, promptTokens.Count + completionTokens.Count));
}

/// <summary>
/// Generate text from a prompt and yield return the result.
/// </summary>
/// <param name="prompt">The prompt to generate text from.</param>
/// <param name="suffix">A suffix to append to the generated text. If None, no suffix is appended.</param>
/// <param name="max_tokens">The maximum number of tokens to generate.</param>
/// <param name="temperature">The temperature to use for sampling.</param>
/// <param name="top_p">The top-p value to use for sampling.</param>
/// <param name="logprobs">The number of logprobs to return. If None, no logprobs are returned.</param>
/// <param name="echo">Whether to echo the prompt.</param>
/// <param name="stop">A list of strings to stop generation when encountered.</param>
/// <param name="frequency_penalty"></param>
/// <param name="presence_penalty"></param>
/// <param name="repeat_penalty">The penalty to apply to repeated tokens.</param>
/// <param name="top_k">The top-k value to use for sampling.</param>
/// <returns></returns>
public IEnumerable<CompletionChunk> CreateCompletionStream(string prompt, string? suffix = null, int max_tokens = 128, float temperature = 0.8f,
float top_p = 0.95f, int logprobs = -1, bool echo = false, string[]? stop = null, float frequency_penalty = .0f,
float presence_penalty = .0f, float repeat_penalty = 1.1f, int top_k = 40)
{
yield return (CompletionChunk)CreateCompletionInternal(prompt, suffix, max_tokens, temperature, top_p, logprobs, echo, stop,
frequency_penalty, presence_penalty, repeat_penalty, top_k, true);
}

/// <summary>
/// Generate text from a prompt.
/// </summary>
/// <param name="prompt">The prompt to generate text from.</param>
/// <param name="suffix">A suffix to append to the generated text. If None, no suffix is appended.</param>
/// <param name="max_tokens">The maximum number of tokens to generate.</param>
/// <param name="temperature">The temperature to use for sampling.</param>
/// <param name="top_p">The top-p value to use for sampling.</param>
/// <param name="logprobs">The number of logprobs to return. If None, no logprobs are returned.</param>
/// <param name="echo">Whether to echo the prompt.</param>
/// <param name="stop">A list of strings to stop generation when encountered.</param>
/// <param name="frequency_penalty"></param>
/// <param name="presence_penalty"></param>
/// <param name="repeat_penalty">The penalty to apply to repeated tokens.</param>
/// <param name="top_k">The top-k value to use for sampling.</param>
/// <returns></returns>
public Completion CreateCompletion(string prompt, string? suffix = null, int max_tokens = 128, float temperature = 0.8f,
float top_p = 0.95f, int logprobs = -1, bool echo = false, string[]? stop = null, float frequency_penalty = .0f,
float presence_penalty = .0f, float repeat_penalty = 1.1f, int top_k = 40)
{
var completion = CreateCompletionInternal(prompt, suffix, max_tokens, temperature, top_p, logprobs, echo, stop,
frequency_penalty, presence_penalty, repeat_penalty, top_k, false).First();
return (Completion)completion;
}

/// <summary>
/// Generate text from a prompt.
/// </summary>
/// <param name="prompt">The prompt to generate text from.</param>
/// <param name="suffix">A suffix to append to the generated text. If None, no suffix is appended.</param>
/// <param name="max_tokens">The maximum number of tokens to generate.</param>
/// <param name="temperature">The temperature to use for sampling.</param>
/// <param name="top_p">The top-p value to use for sampling.</param>
/// <param name="logprobs">The number of logprobs to return. If None, no logprobs are returned.</param>
/// <param name="echo">Whether to echo the prompt.</param>
/// <param name="stop">A list of strings to stop generation when encountered.</param>
/// <param name="frequency_penalty"></param>
/// <param name="presence_penalty"></param>
/// <param name="repeat_penalty">The penalty to apply to repeated tokens.</param>
/// <param name="top_k">The top-k value to use for sampling.</param>
/// <returns></returns>
public Completion Call(string prompt, string? suffix = null, int max_tokens = 128, float temperature = 0.8f,
float top_p = 0.95f, int logprobs = -1, bool echo = false, string[]? stop = null, float frequency_penalty = .0f,
float presence_penalty = .0f, float repeat_penalty = 1.1f, int top_k = 40)
{
return CreateCompletion(prompt, suffix, max_tokens, temperature, top_p, logprobs, echo, stop,
frequency_penalty, presence_penalty, repeat_penalty, top_k);
}

/// <summary>
/// Generate text from a prompt and yield return the result.
/// </summary>
/// <param name="prompt">The prompt to generate text from.</param>
/// <param name="suffix">A suffix to append to the generated text. If None, no suffix is appended.</param>
/// <param name="max_tokens">The maximum number of tokens to generate.</param>
/// <param name="temperature">The temperature to use for sampling.</param>
/// <param name="top_p">The top-p value to use for sampling.</param>
/// <param name="logprobs">The number of logprobs to return. If None, no logprobs are returned.</param>
/// <param name="echo">Whether to echo the prompt.</param>
/// <param name="stop">A list of strings to stop generation when encountered.</param>
/// <param name="frequency_penalty"></param>
/// <param name="presence_penalty"></param>
/// <param name="repeat_penalty">The penalty to apply to repeated tokens.</param>
/// <param name="top_k">The top-k value to use for sampling.</param>
/// <returns></returns>
public IEnumerable<CompletionChunk> StreamCall(string prompt, string? suffix = null, int max_tokens = 128, float temperature = 0.8f,
float top_p = 0.95f, int logprobs = -1, bool echo = false, string[]? stop = null, float frequency_penalty = .0f,
float presence_penalty = .0f, float repeat_penalty = 1.1f, int top_k = 40)
{
return CreateCompletionStream(prompt, suffix, max_tokens, temperature, top_p, logprobs, echo, stop,
frequency_penalty, presence_penalty, repeat_penalty, top_k);
}

private ChatCompletion ConvertTextCompletionToChat(Completion completion)
{
return new ChatCompletion($"chat{completion.Id}", "chat.completion", completion.Created, completion.Model,
new[] { new ChatCompletionChoice(0, new ChatCompletionMessage("assistant", completion.Choices[0].Text, null),
completion.Choices[0].FinishReason) }, completion.Usage);
}

private IEnumerable<ChatCompletionChunk> ConvertTextCompletionChunksToChat(IEnumerable<CompletionChunk> chunks)
{
bool isFirst = true;
foreach(var chunk in chunks)
{
if(isFirst)
{
yield return new ChatCompletionChunk($"chat{chunk.Id}", chunk.Model, "chat.completion.chunk", chunk.Created,
new[] { new ChatCompletionChunkChoice(0, new ChatCompletionChunkDelta("assistant", null), null) });
isFirst = false;
}
yield return new ChatCompletionChunk($"chat{chunk.Id}", chunk.Model, "chat.completion.chunk", chunk.Created,
new[] { new ChatCompletionChunkChoice(0, new ChatCompletionChunkDelta(null, chunk.Choices[0].Text),
chunk.Choices[0].FinishReason) });
}
}

/// <summary>
/// Generate a chat completion from a list of messages.
/// </summary>
/// <param name="messages">A list of messages to generate a response for.</param>
/// <param name="temperature">The temperature to use for sampling.</param>
/// <param name="top_p">The top-p value to use for sampling.</param>
/// <param name="top_k">The top-k value to use for sampling.</param>
/// <param name="stop">A list of strings to stop generation when encountered.</param>
/// <param name="max_tokens">The maximum number of tokens to generate.</param>
/// <param name="presence_penalty"></param>
/// <param name="frequency_penalty"></param>
/// <param name="repeat_penalty">The penalty to apply to repeated tokens.</param>
/// <returns></returns>
public ChatCompletion CreateChatCompletion(IEnumerable<ChatCompletionMessage> messages, float temperature = .2f, float top_p = .95f,
int top_k = 40, string[]? stop = null, int max_tokens = 256, float presence_penalty = .0f, float frequency_penalty = .0f,
float repeat_penalty = 1.1f)
{
if(stop is null)
{
stop = new string[0];
}
string GetRole(ChatCompletionMessage message)
{
return message.Role == "user" ? "Human" : "Assistant";
}
string chatHistory = string.Join("", messages.Select(m => $"### {GetRole(m)}:{m.Content}"));
var prompt = chatHistory + "### Assistant:";
var promptStop = new[] { "### Assistant:", "### Human:" }.Concat(stop).ToArray();
var completion = Call(prompt, stop: promptStop, temperature: temperature, top_p: top_p, top_k: top_k, max_tokens: max_tokens,
repeat_penalty: repeat_penalty, presence_penalty: presence_penalty, frequency_penalty: frequency_penalty);
return ConvertTextCompletionToChat(completion);
}

/// <summary>
/// Generate a chat completion from a list of messages and yield return the result.
/// </summary>
/// <param name="messages">A list of messages to generate a response for.</param>
/// <param name="temperature">The temperature to use for sampling.</param>
/// <param name="top_p">The top-p value to use for sampling.</param>
/// <param name="top_k">The top-k value to use for sampling.</param>
/// <param name="stop">A list of strings to stop generation when encountered.</param>
/// <param name="max_tokens">The maximum number of tokens to generate.</param>
/// <param name="presence_penalty"></param>
/// <param name="frequency_penalty"></param>
/// <param name="repeat_penalty">The penalty to apply to repeated tokens.</param>
/// <returns></returns>
public IEnumerable<ChatCompletionChunk> CreateChatCompletionStream(IEnumerable<ChatCompletionMessage> messages, float temperature = .2f, float top_p = .95f,
int top_k = 40, string[]? stop = null, int max_tokens = 256, float presence_penalty = .0f, float frequency_penalty = .0f,
float repeat_penalty = 1.1f)
{
if (stop is null)
{
stop = new string[0];
}
string GetRole(ChatCompletionMessage message)
{
return message.Role == "user" ? "Human" : "Assistant";
}
string chatHistory = string.Join("", messages.Select(m => $"### {GetRole(m)}:{m.Content}"));
var prompt = chatHistory + "### Assistant:";
var promptStop = new[] { "### Assistant:", "### Human:" }.Concat(stop).ToArray();
var completion = StreamCall(prompt, stop: promptStop, temperature: temperature, top_p: top_p, top_k: top_k, max_tokens: max_tokens,
repeat_penalty: repeat_penalty, presence_penalty: presence_penalty, frequency_penalty: frequency_penalty);
return ConvertTextCompletionChunksToChat(completion);
}

public LLamaState SaveState()
{
Debug.Assert(_ctx.DangerousGetHandle() != IntPtr.Zero);
ulong stateSize = NativeApi.llama_get_state_size(_ctx);
byte[] llamaState = new byte[stateSize];
ulong nBytes = NativeApi.llama_copy_state_data(_ctx, llamaState);
if(nBytes > stateSize)
{
throw new RuntimeError("Failed to copy llama state data");
}
byte[] llamaStateCompact = new byte[nBytes];
llamaState.Take((int)nBytes).ToArray().CopyTo(llamaStateCompact, 0);
if (_verbose)
{
Logger.Default.Info($"Llama.save_state: saving {nBytes} bytes of llama state");
}
return new LLamaState(new Queue<llama_token>(_eval_tokens), new Queue<float[]>(_eval_logits),
llamaStateCompact, (int)nBytes);
}

public void LoadState(LLamaState state)
{
Debug.Assert(_ctx.DangerousGetHandle() != IntPtr.Zero);
_eval_tokens = new Queue<llama_token>(state.EvalTokens);
_eval_logits = new Queue<float[]>(state.EvalLogits);
if(NativeApi.llama_set_state_data(_ctx, state.State) != (ulong)state.Size)
{
throw new RuntimeError($"Failed to set llama state data");
}
}

private static IEnumerable<float> LogitsToLogprobs(IEnumerable<float> logits)
{
var exps = logits.Select(x => (float)Math.Exp(x));
var sumExps = exps.Sum();
return exps.Select(x => (float)Math.Log(x / sumExps));
}

internal static int LongestTokenPrefix(IEnumerable<llama_token> a, IEnumerable<llama_token> b)
{
int longestPrefix = 0;
foreach(var (x, y) in a.Zip(b, (x, y) => (x, y)))
{
if(x == y)
{
longestPrefix++;
}
else
{
break;
}
}
return longestPrefix;
}
}
}

+ 23
- 0
LLama/LLamaSharp.csproj View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<RootNamespace>LLama</RootNamespace>
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
<Platforms>AnyCPU;x64</Platforms>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="IsExternalInit" Version="1.0.3" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Serilog" Version="3.0.0-dev-01998" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.1-dev-00077" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
</ItemGroup>

</Project>

+ 10
- 0
LLama/LLamaState.cs View File

@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace LLama
{
using llama_token = Int32;
public record LLamaState(Queue<llama_token> EvalTokens, Queue<float[]> EvalLogits,
byte[] State, int Size);
}

+ 34
- 0
LLama/LLamaTypes.cs View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace LLama.Types
{
public record EmbeddingUsage(int PromptTokens, int TotalTokens);

public record EmbeddingData(int Index, string Object, float[] Embedding);

public record Embedding(string Object, string Model, EmbeddingData[] Data, EmbeddingUsage Usage);

public record CompletionLogprobs(int[] TextOffset, float[] TokenLogProbs, string[] Tokens, Dictionary<string, float>[] TopLogprobs);

public record CompletionChoice(string Text, int Index, CompletionLogprobs? Logprobs, string? FinishReason);

public record CompletionUsage(int PromptTokens, int CompletionTokens, int TotalTokens);

public record CompletionChunk(string Id, string Object, int Created, string Model, CompletionChoice[] Choices);

public record Completion(string Id, string Object, int Created, string Model, CompletionChoice[] Choices, CompletionUsage Usage);

public record ChatCompletionMessage(string Role, string Content, string? User);

public record ChatCompletionChoice(int Index, ChatCompletionMessage Message, string? FinishReason);

public record ChatCompletion(string Id, string Object, int Created, string Model, ChatCompletionChoice[] Choices, CompletionUsage Usage);

public record ChatCompletionChunkDelta(string? Role, string? Content);

public record ChatCompletionChunkChoice(int Index, ChatCompletionChunkDelta Delta, string? FinishReason);

public record ChatCompletionChunk(string Id, string Model, string Object, int Created, ChatCompletionChunkChoice[] Choices);
}

+ 70
- 0
LLama/Logger.cs View File

@@ -0,0 +1,70 @@
using System;
using Microsoft.Extensions.Logging;
using Serilog;
public sealed class Logger
{
private static readonly Lazy<Logger> _instance = new Lazy<Logger>(() => new Logger());
private static ILoggerFactory _loggerFactory;
private static readonly object _lock = new object();

public static Logger Default => _instance.Value;

private Logger()
{
var logConfig = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}");

_loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddSerilog(logConfig.CreateLogger(), dispose: true);
});
}

public void ToConsole()
{
// 不需要处理,Serilog 默认就输出到控制台
}

public void ToFile(string filename)
{
var logConfig = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}")
.WriteTo.File(filename, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}");

lock (_lock)
{
_loggerFactory.Dispose();

_loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddSerilog(logConfig.CreateLogger(), dispose: true);
});
}
}

public void Info(string message)
{
_loggerFactory.CreateLogger<Logger>().LogInformation(message);
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(message);
Console.ResetColor();
}

public void Warn(string message)
{
_loggerFactory.CreateLogger<Logger>().LogWarning(message);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(message);
Console.ResetColor();
}

public void Error(string message)
{
_loggerFactory.CreateLogger<Logger>().LogError(message);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(message);
Console.ResetColor();
}
}

+ 65
- 0
LLama/Native/LLamaContextParams.cs View File

@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace LLama.Native
{
public delegate void LlamaProgressCallback(float progress, IntPtr ctx);
[StructLayout(LayoutKind.Sequential)]
public struct LLamaContextParams
{
/// <summary>
/// text context
/// </summary>
public int n_ctx;
/// <summary>
/// -1 for default
/// </summary>
public int n_parts;
/// <summary>
/// RNG seed, -1 for random
/// </summary>
public int seed;

/// <summary>
/// use fp16 for KV cache
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool f16_kv;
/// <summary>
/// the llama_eval() call computes all logits, not just the last one
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool logits_all;
/// <summary>
/// only load the vocabulary, no weights
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool vocab_only;
/// <summary>
/// use mmap if possible
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool use_mmap;
/// <summary>
/// force system to keep model in RAM
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool use_mlock;
/// <summary>
/// embedding mode only
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool embedding;

/// <summary>
/// called with a progress value between 0 and 1, pass NULL to disable
/// </summary>
public IntPtr progress_callback;
/// <summary>
/// context pointer passed to the progress callback
/// </summary>
public IntPtr progress_callback_user_data;
}
}

+ 20
- 0
LLama/Native/LLamaFtype.cs View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace LLama.Native
{
internal enum LLamaFtype
{
LLAMA_FTYPE_ALL_F32 = 0,
LLAMA_FTYPE_MOSTLY_F16 = 1, // except 1d tensors
LLAMA_FTYPE_MOSTLY_Q4_0 = 2, // except 1d tensors
LLAMA_FTYPE_MOSTLY_Q4_1 = 3, // except 1d tensors
LLAMA_FTYPE_MOSTLY_Q4_1_SOME_F16 = 4, // tok_embeddings.weight and output.weight are F16
LLAMA_FTYPE_MOSTLY_Q4_2 = 5, // except 1d tensors
// LLAMA_FTYPE_MOSTLY_Q4_3 (6) support has been removed
LLAMA_FTYPE_MOSTLY_Q8_0 = 7, // except 1d tensors
LLAMA_FTYPE_MOSTLY_Q5_0 = 8, // except 1d tensors
LLAMA_FTYPE_MOSTLY_Q5_1 = 9, // except 1d tensors
}
}

+ 31
- 0
LLama/Native/LLamaTokenData.cs View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace LLama.Native
{
[StructLayout(LayoutKind.Sequential)]
internal struct LLamaTokenData
{
/// <summary>
/// token id
/// </summary>
public int id;
/// <summary>
/// log-odds of the token
/// </summary>
public float logit;
/// <summary>
/// probability of the token
/// </summary>
public float p;

public LLamaTokenData(int id, float logit, float p)
{
this.id = id;
this.logit = logit;
this.p = p;
}
}
}

+ 32
- 0
LLama/Native/LLamaTokenDataArray.cs View File

@@ -0,0 +1,32 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace LLama.Native
{
[StructLayout(LayoutKind.Sequential)]
internal struct LLamaTokenDataArray
{
public Memory<LLamaTokenData> data;
public ulong size;
[MarshalAs(UnmanagedType.I1)]
public bool sorted;

public LLamaTokenDataArray(LLamaTokenData[] data, ulong size, bool sorted)
{
this.data = data;
this.size = size;
this.sorted = sorted;
}
}

[StructLayout(LayoutKind.Sequential)]
internal struct LLamaTokenDataArrayNative
{
public IntPtr data;
public ulong size;
public bool sorted;
}
}

+ 128
- 0
LLama/Native/NativeApi.Sampling.cs View File

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace LLama.Native
{
using llama_token = Int32;
internal partial class NativeApi
{
/// <summary>
/// Repetition penalty described in CTRL academic paper https://arxiv.org/abs/1909.05858, with negative logit fix.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <param name="last_tokens"></param>
/// <param name="last_tokens_size"></param>
/// <param name="penalty"></param>
[DllImport(libraryName)]
public static extern void llama_sample_repetition_penalty(SafeLLamaContextHandle ctx, IntPtr candidates, llama_token[] last_tokens, ulong last_tokens_size, float penalty);

/// <summary>
/// Frequency and presence penalties described in OpenAI API https://platform.openai.com/docs/api-reference/parameter-details.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <param name="last_tokens"></param>
/// <param name="last_tokens_size"></param>
/// <param name="alpha_frequency"></param>
/// <param name="alpha_presence"></param>
[DllImport(libraryName)]
public static extern void llama_sample_frequency_and_presence_penalties(SafeLLamaContextHandle ctx, IntPtr candidates, llama_token[] last_tokens, ulong last_tokens_size, float alpha_frequency, float alpha_presence);

/// <summary>
/// Sorts candidate tokens by their logits in descending order and calculate probabilities based on logits.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
[DllImport(libraryName)]
public static extern void llama_sample_softmax(SafeLLamaContextHandle ctx, IntPtr candidates);

/// <summary>
/// Top-K sampling described in academic paper "The Curious Case of Neural Text Degeneration" https://arxiv.org/abs/1904.09751
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <param name="k"></param>
/// <param name="min_keep"></param>
[DllImport(libraryName)]
public static extern void llama_sample_top_k(SafeLLamaContextHandle ctx, IntPtr candidates, int k, ulong min_keep);

/// <summary>
/// Nucleus sampling described in academic paper "The Curious Case of Neural Text Degeneration" https://arxiv.org/abs/1904.09751
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <param name="p"></param>
/// <param name="min_keep"></param>
[DllImport(libraryName)]
public static extern void llama_sample_top_p(SafeLLamaContextHandle ctx, IntPtr candidates, float p, ulong min_keep);

/// <summary>
/// Tail Free Sampling described in https://www.trentonbricken.com/Tail-Free-Sampling/.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <param name="z"></param>
/// <param name="min_keep"></param>
[DllImport(libraryName)]
public static extern void llama_sample_tail_free(SafeLLamaContextHandle ctx, IntPtr candidates, float z, ulong min_keep);

/// <summary>
/// Locally Typical Sampling implementation described in the paper https://arxiv.org/abs/2202.00666.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <param name="p"></param>
/// <param name="min_keep"></param>
[DllImport(libraryName)]
public static extern void llama_sample_typical(SafeLLamaContextHandle ctx, IntPtr candidates, float p, ulong min_keep);

[DllImport(libraryName)]
public static extern void llama_sample_temperature(SafeLLamaContextHandle ctx, IntPtr candidates, float temp);

/// <summary>
/// Mirostat 1.0 algorithm described in the paper https://arxiv.org/abs/2007.14966. Uses tokens instead of words.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">A vector of `llama_token_data` containing the candidate tokens, their probabilities (p), and log-odds (logit) for the current position in the generated text.</param>
/// <param name="tau">The target cross-entropy (or surprise) value you want to achieve for the generated text. A higher value corresponds to more surprising or less predictable text, while a lower value corresponds to less surprising or more predictable text.</param>
/// <param name="eta">The learning rate used to update `mu` based on the error between the target and observed surprisal of the sampled word. A larger learning rate will cause `mu` to be updated more quickly, while a smaller learning rate will result in slower updates.</param>
/// <param name="m">The number of tokens considered in the estimation of `s_hat`. This is an arbitrary value that is used to calculate `s_hat`, which in turn helps to calculate the value of `k`. In the paper, they use `m = 100`, but you can experiment with different values to see how it affects the performance of the algorithm.</param>
/// <param name="mu">Maximum cross-entropy. This value is initialized to be twice the target cross-entropy (`2 * tau`) and is updated in the algorithm based on the error between the target and observed surprisal.</param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern llama_token llama_sample_token_mirostat(SafeLLamaContextHandle ctx, IntPtr candidates, float tau, float eta, int m, float[] mu);

/// <summary>
/// Mirostat 2.0 algorithm described in the paper https://arxiv.org/abs/2007.14966. Uses tokens instead of words.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">A vector of `llama_token_data` containing the candidate tokens, their probabilities (p), and log-odds (logit) for the current position in the generated text.</param>
/// <param name="tau">The target cross-entropy (or surprise) value you want to achieve for the generated text. A higher value corresponds to more surprising or less predictable text, while a lower value corresponds to less surprising or more predictable text.</param>
/// <param name="eta">The learning rate used to update `mu` based on the error between the target and observed surprisal of the sampled word. A larger learning rate will cause `mu` to be updated more quickly, while a smaller learning rate will result in slower updates.</param>
/// <param name="mu">Maximum cross-entropy. This value is initialized to be twice the target cross-entropy (`2 * tau`) and is updated in the algorithm based on the error between the target and observed surprisal.</param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern llama_token llama_sample_token_mirostat_v2(SafeLLamaContextHandle ctx, IntPtr candidates, float tau, float eta, float[] mu);

/// <summary>
/// Selects the token with the highest probability.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern llama_token llama_sample_token_greedy(SafeLLamaContextHandle ctx, IntPtr candidates);

/// <summary>
/// Randomly selects a token from the candidates based on their probabilities.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern llama_token llama_sample_token(SafeLLamaContextHandle ctx, IntPtr candidates);
}
}

+ 226
- 0
LLama/Native/NativeApi.cs View File

@@ -0,0 +1,226 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace LLama.Native
{
using llama_token = Int32;
internal unsafe partial class NativeApi
{
private const string libraryName = "llama";

[DllImport(libraryName)]
public static extern LLamaContextParams llama_context_default_params();

[DllImport(libraryName)]
public static extern bool llama_mmap_supported();

[DllImport(libraryName)]
public static extern bool llama_mlock_supported();

/// <summary>
/// Various functions for loading a ggml llama model.
/// Allocate (almost) all memory needed for the model.
/// Return NULL on failure
/// </summary>
/// <param name="path_model"></param>
/// <param name="params_"></param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern IntPtr llama_init_from_file(string path_model, LLamaContextParams params_);

/// <summary>
/// Frees all allocated memory
/// </summary>
/// <param name="ctx"></param>
[DllImport(libraryName)]
public static extern void llama_free(IntPtr ctx);

/// <summary>
/// Returns 0 on success
/// </summary>
/// <param name="fname_inp"></param>
/// <param name="fname_out"></param>
/// <param name="ftype"></param>
/// <param name="nthread">how many threads to use. If <=0, will use std::thread::hardware_concurrency(), else the number given</param>
/// <remarks>not great API - very likely to change</remarks>
/// <returns>Returns 0 on success</returns>
[DllImport(libraryName)]
public static extern int llama_model_quantize(string fname_inp, string fname_out, LLamaFtype ftype, int nthread);

/// <summary>
/// Apply a LoRA adapter to a loaded model
/// path_base_model is the path to a higher quality model to use as a base for
/// the layers modified by the adapter. Can be NULL to use the current loaded model.
/// The model needs to be reloaded before applying a new adapter, otherwise the adapter
/// will be applied on top of the previous one
/// </summary>
/// <param name="ctx"></param>
/// <param name="path_lora"></param>
/// <param name="path_base_model"></param>
/// <param name="n_threads"></param>
/// <returns>Returns 0 on success</returns>
[DllImport(libraryName)]
public static extern int llama_apply_lora_from_file(SafeLLamaContextHandle ctx, string path_lora, string path_base_model, int n_threads);

/// <summary>
/// Returns the number of tokens in the KV cache
/// </summary>
/// <param name="ctx"></param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern int llama_get_kv_cache_token_count(SafeLLamaContextHandle ctx);

/// <summary>
/// Sets the current rng seed.
/// </summary>
/// <param name="ctx"></param>
/// <param name="seed"></param>
[DllImport(libraryName)]
public static extern void llama_set_rng_seed(SafeLLamaContextHandle ctx, int seed);

/// <summary>
/// Returns the maximum size in bytes of the state (rng, logits, embedding
/// and kv_cache) - will often be smaller after compacting tokens
/// </summary>
/// <param name="ctx"></param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern ulong llama_get_state_size(SafeLLamaContextHandle ctx);

/// <summary>
/// Copies the state to the specified destination address.
/// Destination needs to have allocated enough memory.
/// Returns the number of bytes copied
/// </summary>
/// <param name="ctx"></param>
/// <param name="dest"></param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern ulong llama_copy_state_data(SafeLLamaContextHandle ctx, byte[] dest);

/// <summary>
/// Set the state reading from the specified address
/// Returns the number of bytes read
/// </summary>
/// <param name="ctx"></param>
/// <param name="src"></param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern ulong llama_set_state_data(SafeLLamaContextHandle ctx, byte[] src);

/// <summary>
/// Load session file
/// </summary>
/// <param name="ctx"></param>
/// <param name="path_session"></param>
/// <param name="tokens_out"></param>
/// <param name="n_token_capacity"></param>
/// <param name="n_token_count_out"></param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern bool llama_load_session_file(SafeLLamaContextHandle ctx, string path_session, llama_token[] tokens_out, ulong n_token_capacity, ulong[] n_token_count_out);

/// <summary>
/// Save session file
/// </summary>
/// <param name="ctx"></param>
/// <param name="path_session"></param>
/// <param name="tokens"></param>
/// <param name="n_token_count"></param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern bool llama_save_session_file(SafeLLamaContextHandle ctx, string path_session, llama_token[] tokens, ulong n_token_count);

/// <summary>
/// Run the llama inference to obtain the logits and probabilities for the next token.
/// tokens + n_tokens is the provided batch of new tokens to process
/// n_past is the number of tokens to use from previous eval calls
/// </summary>
/// <param name="ctx"></param>
/// <param name="tokens"></param>
/// <param name="n_tokens"></param>
/// <param name="n_past"></param>
/// <param name="n_threads"></param>
/// <returns>Returns 0 on success</returns>
[DllImport(libraryName)]
public static extern int llama_eval(SafeLLamaContextHandle ctx, llama_token[] tokens, int n_tokens, int n_past, int n_threads);

/// <summary>
/// Convert the provided text into tokens.
/// The tokens pointer must be large enough to hold the resulting tokens.
/// Returns the number of tokens on success, no more than n_max_tokens
/// Returns a negative number on failure - the number of tokens that would have been returned
/// </summary>
/// <param name="ctx"></param>
/// <param name="text"></param>
/// <param name="tokens"></param>
/// <param name="n_max_tokens"></param>
/// <param name="add_bos"></param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern int llama_tokenize(SafeLLamaContextHandle ctx, string text, llama_token[] tokens, int n_max_tokens, bool add_bos);

[DllImport(libraryName)]
public static extern int llama_n_vocab(SafeLLamaContextHandle ctx);

[DllImport(libraryName)]
public static extern int llama_n_ctx(SafeLLamaContextHandle ctx);

[DllImport(libraryName)]
public static extern int llama_n_embd(SafeLLamaContextHandle ctx);

/// <summary>
/// Token logits obtained from the last call to llama_eval()
/// The logits for the last token are stored in the last row
/// Can be mutated in order to change the probabilities of the next token
/// Rows: n_tokens
/// Cols: n_vocab
/// </summary>
/// <param name="ctx"></param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern float* llama_get_logits(SafeLLamaContextHandle ctx);

/// <summary>
/// Get the embeddings for the input
/// shape: [n_embd] (1-dimensional)
/// </summary>
/// <param name="ctx"></param>
/// <returns></returns>
[DllImport(libraryName)]
public static extern float* llama_get_embeddings(SafeLLamaContextHandle ctx);

/// <summary>
/// Token Id -> String. Uses the vocabulary in the provided context
/// </summary>
/// <param name="ctx"></param>
/// <param name="token"></param>
/// <returns>Pointer to a string.</returns>
[DllImport(libraryName)]
public static extern IntPtr llama_token_to_str(SafeLLamaContextHandle ctx, llama_token token);

[DllImport(libraryName)]
public static extern llama_token llama_token_bos();

[DllImport(libraryName)]
public static extern llama_token llama_token_eos();

[DllImport(libraryName)]
public static extern llama_token llama_token_nl();

[DllImport(libraryName)]
public static extern void llama_print_timings(SafeLLamaContextHandle ctx);

[DllImport(libraryName)]
public static extern void llama_reset_timings(SafeLLamaContextHandle ctx);

/// <summary>
/// Print system information
/// </summary>
/// <returns></returns>
[DllImport(libraryName)]
public static extern IntPtr llama_print_system_info();
}
}

+ 15
- 0
LLama/Native/NativeInfo.cs View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace LLama.Native
{
internal class NativeInfo
{
internal static readonly int LLAMA_FILE_VERSION = 1;
internal static readonly string LLAMA_FILE_MAGIC = "ggjt";
internal static readonly string LLAMA_FILE_MAGIC_UNVERSIONED = "ggml";
internal static readonly string LLAMA_SESSION_MAGIC = "ggsn";
internal static readonly int LLAMA_SESSION_VERSION = 1;
}
}

+ 26
- 0
LLama/Native/SafeLLamaContextHandle.cs View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace LLama.Native
{
internal class SafeLLamaContextHandle: SafeLLamaHandleBase
{
protected SafeLLamaContextHandle()
{
}

public SafeLLamaContextHandle(IntPtr handle)
: base(handle)
{
}

protected override bool ReleaseHandle()
{
NativeApi.llama_free(handle);
SetHandle(IntPtr.Zero);
return true;
}
}
}

+ 32
- 0
LLama/Native/SafeLLamaHandleBase.cs View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace LLama.Native
{
internal abstract class SafeLLamaHandleBase: SafeHandle
{
private protected SafeLLamaHandleBase()
: base(IntPtr.Zero, ownsHandle: true)
{
}

private protected SafeLLamaHandleBase(IntPtr handle)
: base(IntPtr.Zero, ownsHandle: true)
{
SetHandle(handle);
}

private protected SafeLLamaHandleBase(IntPtr handle, bool ownsHandle)
: base(IntPtr.Zero, ownsHandle)
{
SetHandle(handle);
}

public override bool IsInvalid => handle == IntPtr.Zero;

public override string ToString()
=> $"0x{handle.ToString("x16")}";
}
}

+ 212
- 0
LLama/Native/SamplingApi.cs View File

@@ -0,0 +1,212 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace LLama.Native
{
using llama_token = Int32;
internal unsafe class SamplingApi
{
/// <summary>
/// Repetition penalty described in CTRL academic paper https://arxiv.org/abs/1909.05858, with negative logit fix.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <param name="last_tokens"></param>
/// <param name="last_tokens_size"></param>
/// <param name="penalty"></param>
public static void llama_sample_repetition_penalty(SafeLLamaContextHandle ctx, LLamaTokenDataArray candidates, llama_token[] last_tokens, ulong last_tokens_size, float penalty)
{
var handle = candidates.data.Pin();
var st = new LLamaTokenDataArrayNative();
st.data = new IntPtr(handle.Pointer);
st.size = candidates.size;
st.sorted = candidates.sorted;
NativeApi.llama_sample_repetition_penalty(ctx, new IntPtr(&st), last_tokens, last_tokens_size, penalty);
}

/// <summary>
/// Frequency and presence penalties described in OpenAI API https://platform.openai.com/docs/api-reference/parameter-details.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <param name="last_tokens"></param>
/// <param name="last_tokens_size"></param>
/// <param name="alpha_frequency"></param>
/// <param name="alpha_presence"></param>
public static void llama_sample_frequency_and_presence_penalties(SafeLLamaContextHandle ctx, LLamaTokenDataArray candidates, llama_token[] last_tokens, ulong last_tokens_size, float alpha_frequency, float alpha_presence)
{
var handle = candidates.data.Pin();
var st = new LLamaTokenDataArrayNative();
st.data = new IntPtr(handle.Pointer);
st.size = candidates.size;
st.sorted = candidates.sorted;
NativeApi.llama_sample_frequency_and_presence_penalties(ctx, new IntPtr(&st), last_tokens, last_tokens_size, alpha_frequency, alpha_presence);
}

/// <summary>
/// Sorts candidate tokens by their logits in descending order and calculate probabilities based on logits.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
public static void llama_sample_softmax(SafeLLamaContextHandle ctx, LLamaTokenDataArray candidates)
{
var handle = candidates.data.Pin();
var st = new LLamaTokenDataArrayNative();
st.data = new IntPtr(handle.Pointer);
st.size = candidates.size;
st.sorted = candidates.sorted;
NativeApi.llama_sample_softmax(ctx, new IntPtr(&st));
}

/// <summary>
/// Top-K sampling described in academic paper "The Curious Case of Neural Text Degeneration" https://arxiv.org/abs/1904.09751
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <param name="k"></param>
/// <param name="min_keep"></param>
public static void llama_sample_top_k(SafeLLamaContextHandle ctx, LLamaTokenDataArray candidates, int k, ulong min_keep)
{
var handle = candidates.data.Pin();
var st = new LLamaTokenDataArrayNative();
st.data = new IntPtr(handle.Pointer);
st.size = candidates.size;
st.sorted = candidates.sorted;
NativeApi.llama_sample_top_k(ctx, new IntPtr(&st), k, min_keep);
}

/// <summary>
/// Nucleus sampling described in academic paper "The Curious Case of Neural Text Degeneration" https://arxiv.org/abs/1904.09751
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <param name="p"></param>
/// <param name="min_keep"></param>
public static void llama_sample_top_p(SafeLLamaContextHandle ctx, LLamaTokenDataArray candidates, float p, ulong min_keep)
{
var handle = candidates.data.Pin();
var st = new LLamaTokenDataArrayNative();
st.data = new IntPtr(handle.Pointer);
st.size = candidates.size;
st.sorted = candidates.sorted;
NativeApi.llama_sample_top_p(ctx, new IntPtr(&st), p, min_keep);
}

/// <summary>
/// Tail Free Sampling described in https://www.trentonbricken.com/Tail-Free-Sampling/.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <param name="z"></param>
/// <param name="min_keep"></param>
public static void llama_sample_tail_free(SafeLLamaContextHandle ctx, LLamaTokenDataArray candidates, float z, ulong min_keep)
{
var handle = candidates.data.Pin();
var st = new LLamaTokenDataArrayNative();
st.data = new IntPtr(handle.Pointer);
st.size = candidates.size;
st.sorted = candidates.sorted;
NativeApi.llama_sample_tail_free(ctx, new IntPtr(&st), z, min_keep);
}

/// <summary>
/// Locally Typical Sampling implementation described in the paper https://arxiv.org/abs/2202.00666.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <param name="p"></param>
/// <param name="min_keep"></param>
public static void llama_sample_typical(SafeLLamaContextHandle ctx, LLamaTokenDataArray candidates, float p, ulong min_keep)
{
var handle = candidates.data.Pin();
var st = new LLamaTokenDataArrayNative();
st.data = new IntPtr(handle.Pointer);
st.size = candidates.size;
st.sorted = candidates.sorted;
NativeApi.llama_sample_typical(ctx, new IntPtr(&st), p, min_keep);
}

public static void llama_sample_temperature(SafeLLamaContextHandle ctx, LLamaTokenDataArray candidates, float temp)
{
var handle = candidates.data.Pin();
var st = new LLamaTokenDataArrayNative();
st.data = new IntPtr(handle.Pointer);
st.size = candidates.size;
st.sorted = candidates.sorted;
NativeApi.llama_sample_temperature(ctx, new IntPtr(&st), temp);
}

/// <summary>
/// Mirostat 1.0 algorithm described in the paper https://arxiv.org/abs/2007.14966. Uses tokens instead of words.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">A vector of `LLamaTokenData` containing the candidate tokens, their probabilities (p), and log-odds (logit) for the current position in the generated text.</param>
/// <param name="tau">The target cross-entropy (or surprise) value you want to achieve for the generated text. A higher value corresponds to more surprising or less predictable text, while a lower value corresponds to less surprising or more predictable text.</param>
/// <param name="eta">The learning rate used to update `mu` based on the error between the target and observed surprisal of the sampled word. A larger learning rate will cause `mu` to be updated more quickly, while a smaller learning rate will result in slower updates.</param>
/// <param name="m">The number of tokens considered in the estimation of `s_hat`. This is an arbitrary value that is used to calculate `s_hat`, which in turn helps to calculate the value of `k`. In the paper, they use `m = 100`, but you can experiment with different values to see how it affects the performance of the algorithm.</param>
/// <param name="mu">Maximum cross-entropy. This value is initialized to be twice the target cross-entropy (`2 * tau`) and is updated in the algorithm based on the error between the target and observed surprisal.</param>
/// <returns></returns>
public static llama_token llama_sample_token_mirostat(SafeLLamaContextHandle ctx, LLamaTokenDataArray candidates, float tau, float eta, int m, float[] mu)
{
var handle = candidates.data.Pin();
var st = new LLamaTokenDataArrayNative();
st.data = new IntPtr(handle.Pointer);
st.size = candidates.size;
st.sorted = candidates.sorted;
return NativeApi.llama_sample_token_mirostat(ctx, new IntPtr(&st), tau, eta, m, mu);
}

/// <summary>
/// Mirostat 2.0 algorithm described in the paper https://arxiv.org/abs/2007.14966. Uses tokens instead of words.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">A vector of `LLamaTokenData` containing the candidate tokens, their probabilities (p), and log-odds (logit) for the current position in the generated text.</param>
/// <param name="tau">The target cross-entropy (or surprise) value you want to achieve for the generated text. A higher value corresponds to more surprising or less predictable text, while a lower value corresponds to less surprising or more predictable text.</param>
/// <param name="eta">The learning rate used to update `mu` based on the error between the target and observed surprisal of the sampled word. A larger learning rate will cause `mu` to be updated more quickly, while a smaller learning rate will result in slower updates.</param>
/// <param name="mu">Maximum cross-entropy. This value is initialized to be twice the target cross-entropy (`2 * tau`) and is updated in the algorithm based on the error between the target and observed surprisal.</param>
/// <returns></returns>
public static llama_token llama_sample_token_mirostat_v2(SafeLLamaContextHandle ctx, LLamaTokenDataArray candidates, float tau, float eta, float[] mu)
{
var handle = candidates.data.Pin();
var st = new LLamaTokenDataArrayNative();
st.data = new IntPtr(handle.Pointer);
st.size = candidates.size;
st.sorted = candidates.sorted;
return NativeApi.llama_sample_token_mirostat_v2(ctx, new IntPtr(&st), tau, eta, mu);
}

/// <summary>
/// Selects the token with the highest probability.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <returns></returns>
public static llama_token llama_sample_token_greedy(SafeLLamaContextHandle ctx, LLamaTokenDataArray candidates)
{
var handle = candidates.data.Pin();
var st = new LLamaTokenDataArrayNative();
st.data = new IntPtr(handle.Pointer);
st.size = candidates.size;
st.sorted = candidates.sorted;
return NativeApi.llama_sample_token_greedy(ctx, new IntPtr(&st));
}

/// <summary>
/// Randomly selects a token from the candidates based on their probabilities.
/// </summary>
/// <param name="ctx"></param>
/// <param name="candidates">Pointer to LLamaTokenDataArray</param>
/// <returns></returns>
public static llama_token llama_sample_token(SafeLLamaContextHandle ctx, LLamaTokenDataArray candidates)
{
var handle = candidates.data.Pin();
var st = new LLamaTokenDataArrayNative();
st.data = new IntPtr(handle.Pointer);
st.size = candidates.size;
st.sorted = candidates.sorted;
return NativeApi.llama_sample_token(ctx, new IntPtr(&st));
}
}
}

+ 51
- 0
LLamaSharp.sln View File

@@ -0,0 +1,51 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33424.131
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LLama.Console", "LLama.Console\LLama.Console.csproj", "{BE303C09-6532-4560-A136-D678C40B0FE6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LLamaSharp", "LLama\LLamaSharp.csproj", "{2B5A6DEB-FEC2-44C3-AD32-5B4A26A43026}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LLama.Unittest", "LLama.Unittest\LLama.Unittest.csproj", "{BAC1CFA9-E6AC-4BD0-A548-A8066D3C467E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BE303C09-6532-4560-A136-D678C40B0FE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BE303C09-6532-4560-A136-D678C40B0FE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE303C09-6532-4560-A136-D678C40B0FE6}.Debug|x64.ActiveCfg = Debug|x64
{BE303C09-6532-4560-A136-D678C40B0FE6}.Debug|x64.Build.0 = Debug|x64
{BE303C09-6532-4560-A136-D678C40B0FE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE303C09-6532-4560-A136-D678C40B0FE6}.Release|Any CPU.Build.0 = Release|Any CPU
{BE303C09-6532-4560-A136-D678C40B0FE6}.Release|x64.ActiveCfg = Release|x64
{BE303C09-6532-4560-A136-D678C40B0FE6}.Release|x64.Build.0 = Release|x64
{2B5A6DEB-FEC2-44C3-AD32-5B4A26A43026}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B5A6DEB-FEC2-44C3-AD32-5B4A26A43026}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B5A6DEB-FEC2-44C3-AD32-5B4A26A43026}.Debug|x64.ActiveCfg = Debug|x64
{2B5A6DEB-FEC2-44C3-AD32-5B4A26A43026}.Debug|x64.Build.0 = Debug|x64
{2B5A6DEB-FEC2-44C3-AD32-5B4A26A43026}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B5A6DEB-FEC2-44C3-AD32-5B4A26A43026}.Release|Any CPU.Build.0 = Release|Any CPU
{2B5A6DEB-FEC2-44C3-AD32-5B4A26A43026}.Release|x64.ActiveCfg = Release|x64
{2B5A6DEB-FEC2-44C3-AD32-5B4A26A43026}.Release|x64.Build.0 = Release|x64
{BAC1CFA9-E6AC-4BD0-A548-A8066D3C467E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAC1CFA9-E6AC-4BD0-A548-A8066D3C467E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAC1CFA9-E6AC-4BD0-A548-A8066D3C467E}.Debug|x64.ActiveCfg = Debug|x64
{BAC1CFA9-E6AC-4BD0-A548-A8066D3C467E}.Debug|x64.Build.0 = Debug|x64
{BAC1CFA9-E6AC-4BD0-A548-A8066D3C467E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAC1CFA9-E6AC-4BD0-A548-A8066D3C467E}.Release|Any CPU.Build.0 = Release|Any CPU
{BAC1CFA9-E6AC-4BD0-A548-A8066D3C467E}.Release|x64.ActiveCfg = Release|x64
{BAC1CFA9-E6AC-4BD0-A548-A8066D3C467E}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87746B65-249E-4AD9-882B-7B919D079367}
EndGlobalSection
EndGlobal

Loading…
Cancel
Save