Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6f4c53c
add LLamaReranker and tests
nipeone Apr 3, 2025
a69f814
Merge branch 'feature-llamareranker'
nipeone Apr 3, 2025
15c5247
Merge branch 'SciSharp:master' into feature-llamareranker
nipeone Apr 11, 2025
c604359
optimize LLamaReranker function
nipeone Apr 11, 2025
d99670c
fix Reranking if documents is too large
nipeone Apr 11, 2025
05677fe
fix Reranking if document contains null
nipeone Apr 15, 2025
4258cc1
Merge branch 'SciSharp:master' into feature-llamareranker
nipeone Apr 18, 2025
8d61a92
Merge branch 'SciSharp:master' into feature-llamareranker
nipeone Apr 21, 2025
e1939eb
Merge branch 'SciSharp:master' into master
nipeone Apr 29, 2025
49ae0a8
Merge branch 'SciSharp:master' into feature-llamareranker
nipeone Apr 29, 2025
474cfd1
Merge branch 'SciSharp:master' into master
nipeone May 6, 2025
a53f503
Merge branch 'master' of https://github.com/nipeone/LLamaSharp
nipeone May 6, 2025
9ed7378
Merge upstream/master and resolve conflicts
nipeone May 6, 2025
37bb3c3
Merge branch 'master' into feature-llamareranker
nipeone May 6, 2025
2bcb62e
Merge branch 'SciSharp:master' into master
nipeone May 7, 2025
69a5f42
Merge branch 'SciSharp:master' into feature-llamareranker
nipeone May 7, 2025
7b2ee55
Merge remote-tracking branch 'upstream/master'
nipeone May 12, 2025
8a34866
Merge remote-tracking branch 'upstream/master'
nipeone May 12, 2025
371fdcd
optimize LLamaReranker function
nipeone Apr 11, 2025
87059e8
fix Reranking if documents is too large
nipeone Apr 11, 2025
14ba50f
fix Reranking if document contains null
nipeone Apr 15, 2025
9f4bd96
optimize LLamaReranker function
nipeone Apr 11, 2025
8fde3bc
Merge branch 'feature-llamareranker' of https://github.com/nipeone/LL…
nipeone May 12, 2025
3f51a7f
Merge branch 'SciSharp:master' into feature-llamareranker
nipeone May 12, 2025
63ae374
fix code comments in llamareranker file
nipeone May 12, 2025
d838e1c
implement IDisposable in LLamaRerankerTests and SafeLlamaModelHandleV…
nipeone May 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LLama.Unittest/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ internal static class Constants
public static readonly string GenerativeModelPath = "Models/Llama-3.2-1B-Instruct-Q4_0.gguf";
public static readonly string GenerativeModelPath2 = "Models/smollm-360m-instruct-add-basics-q8_0.gguf";
public static readonly string EmbeddingModelPath = "Models/all-MiniLM-L12-v2.Q8_0.gguf";
public static readonly string RerankingModelPath = "Models/jina-reranker-v1-tiny-en-FP16.gguf";

public static readonly string LLavaModelPath = "Models/llava-v1.6-mistral-7b.Q3_K_XS.gguf";
public static readonly string LLavaMmpPath = "Models/mmproj-model-f16.gguf";
Expand Down
10 changes: 8 additions & 2 deletions LLama.Unittest/LLama.Unittest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@

<DownloadFile SourceUrl="https://huggingface.co/HuggingFaceTB/smollm-360M-instruct-v0.2-Q8_0-GGUF/resolve/main/smollm-360m-instruct-add-basics-q8_0.gguf" DestinationFolder="Models" DestinationFileName="smollm-360m-instruct-add-basics-q8_0.gguf" SkipUnchangedFiles="true">
</DownloadFile>

<DownloadFile SourceUrl="https://huggingface.co/cjpais/llava-1.6-mistral-7b-gguf/resolve/main/llava-v1.6-mistral-7b.Q3_K_XS.gguf" DestinationFolder="Models" DestinationFileName="llava-v1.6-mistral-7b.Q3_K_XS.gguf" SkipUnchangedFiles="true">

<DownloadFile SourceUrl="https://huggingface.co/gpustack/jina-reranker-v1-tiny-en-GGUF/resolve/main/jina-reranker-v1-tiny-en-FP16.gguf" DestinationFolder="Models" DestinationFileName="jina-reranker-v1-tiny-en-FP16.gguf" SkipUnchangedFiles="true">
</DownloadFile>

<DownloadFile SourceUrl="https://huggingface.co/cjpais/llava-1.6-mistral-7b-gguf/resolve/main/llava-v1.6-mistral-7b.Q3_K_XS.gguf" DestinationFolder="Models" DestinationFileName="llava-v1.6-mistral-7b.Q3_K_XS.gguf" SkipUnchangedFiles="true">
</DownloadFile>

<DownloadFile SourceUrl="https://huggingface.co/cjpais/llava-1.6-mistral-7b-gguf/resolve/main/mmproj-model-f16.gguf" DestinationFolder="Models" DestinationFileName="mmproj-model-f16.gguf" SkipUnchangedFiles="true">
Expand Down Expand Up @@ -63,6 +66,9 @@
<None Update="Models\Llama-3.2-1B-Instruct-Q4_0.gguf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Models\jina-reranker-v1-tiny-en-FP16.gguf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Models\smollm-360m-instruct-add-basics-q8_0.gguf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
74 changes: 74 additions & 0 deletions LLama.Unittest/LLamaRerankerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using LLama.Common;
using LLama.Extensions;
using LLama.Native;
using Microsoft.Extensions.AI;
using System.Runtime.InteropServices;
using Xunit.Abstractions;

namespace LLama.Unittest;

public sealed class LLamaRerankerTests
{
private readonly ITestOutputHelper _testOutputHelper;
private readonly LLamaReranker _reranker;
public LLamaRerankerTests(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;

var @params = new ModelParams(Constants.RerankingModelPath)
{
ContextSize = 0,
PoolingType = LLamaPoolingType.Rank,
GpuLayerCount = Constants.CIGpuLayerCount,

};
using var weights = LLamaWeights.LoadFromFile(@params);
_reranker = new LLamaReranker(weights, @params);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weights is disposed when exiting this method, but _reranker holds onto a reference and uses it later. Should probably make this class disposable (e.g. see https://github.com/SciSharp/LLamaSharp/blob/master/LLama.Unittest/BasicTest.cs#L13-L27)

}

[Fact]
public async Task CompareRerankingScore()
{


var input = "what is panda?";
var documents = new string[] {
"hi",
"it's a bear",
string.Join(", ","The giant panda (Ailuropoda melanoleuca)",
"sometimes called a panda bear or simply panda",
"is a bear species endemic to China.")
};
var scores = await _reranker.GetRelevanceScores(input, documents, normalize: false);

Assert.True(documents.Length == scores.Count);

_testOutputHelper.WriteLine($"Rerank score 0: {scores[0]:F4}");
_testOutputHelper.WriteLine($"Rerank score 1: {scores[1]:F4}");
_testOutputHelper.WriteLine($"Rerank score 2: {scores[2]:F4}");
}

[Fact]
public async Task MostRelevantDocument()
{
var input = "what is panda?";
var documents = new string[] {
"hi",
"it's a bear",
string.Join(", ","The giant panda (Ailuropoda melanoleuca)",
"sometimes called a panda bear or simply panda",
"is a bear species endemic to China.")
};
var scores = await _reranker.GetRelevanceScores(input, documents, normalize: true);

Assert.True(documents.Length == scores.Count);

int maxIndex = scores
.Select((score, index) => new { Score = score, Index = index })
.MaxBy(x => x.Score)
.Index;

var maxScoreDocument = documents[maxIndex];
Assert.Equal(documents[2], maxScoreDocument);
}
}
37 changes: 37 additions & 0 deletions LLama.Unittest/Native/SafeLlamaModelHandleVocabularyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Text;
using System.Xml.Linq;
using LLama.Common;
using LLama.Extensions;
using Microsoft.Extensions.Logging;


namespace LLama.Unittest.Native;

public class SafeLlamaModelHandleVocabularyTests
{
private readonly LLamaWeights _model;

public SafeLlamaModelHandleVocabularyTests()
{
var @params = new ModelParams(Constants.RerankingModelPath)
{
ContextSize = 0,
PoolingType = LLama.Native.LLamaPoolingType.Rank,
GpuLayerCount = Constants.CIGpuLayerCount
};
_model = LLamaWeights.LoadFromFile(@params);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_model is never disposed, should probably make this class disposable (see https://github.com/SciSharp/LLamaSharp/blob/master/LLama.Unittest/BasicTest.cs#L13-L27)

}

[Fact]
public void GetLLamaTokenString()
{
var bos = _model.Vocab.BOS;
var eos = _model.Vocab.EOS;

var bosStr = _model.Vocab.LLamaTokenToString(bos, true);
var eosStr = _model.Vocab.LLamaTokenToString(eos, true);

Assert.Equal("<s>", bosStr);
Assert.Equal("</s>", eosStr);
}
}
137 changes: 137 additions & 0 deletions LLama/LLamaReranker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using LLama.Abstractions;
using LLama.Exceptions;
using LLama.Native;
using Microsoft.Extensions.Logging;

namespace LLama;

/// <summary>
/// Get rank scores between prompt and documents
/// </summary>
public sealed partial class LLamaReranker
: IDisposable
{
/// <summary>
/// string BOS
/// </summary>
public string StrBOS { get; }
/// <summary>
/// string EOS
/// </summary>
public string StrEOS { get; }


/// <summary>
/// Dimension of embedding vectors
/// </summary>
public int EmbeddingSize => Context.EmbeddingSize;

/// <summary>
/// LLama Context
/// </summary>
public LLamaContext Context { get; }

/// <summary>
/// Create a new reranker, using the given LLamaWeights
/// </summary>
/// <param name="weights"></param>
/// <param name="params"></param>
/// <param name="logger"></param>
public LLamaReranker(LLamaWeights weights, IContextParams @params, ILogger? logger = null)
{
if (@params.UBatchSize != @params.BatchSize)
throw new ArgumentException("For non-causal models, batch size must be equal to ubatch size", nameof(@params));
if (weights.NativeHandle is { HasEncoder: true, HasDecoder: true })
throw new NotSupportedException("Computing rank in encoder-decoder models is not supported");
if (@params.PoolingType != LLamaPoolingType.Rank)
throw new NotSupportedException("Computing rank score, PoolingType must be equal to LLamaPoolingType.Rank");
Context = weights.CreateContext(@params, logger);
NativeApi.llama_set_embeddings(Context.NativeHandle, true);
StrBOS = Context.Vocab.LLamaTokenToString(Context.Vocab.BOS, true) ?? "<s>";
StrEOS = Context.Vocab.LLamaTokenToString(Context.Vocab.EOS, true) ?? "</s>";
}

/// <inheritdoc />
public void Dispose()
{
Context.Dispose();
}

/// <summary>
/// Retrieve relevance scores for input and document by reranking
/// </summary>
/// <param name="input"></param>
/// <param name="documents"></param>
/// <param name="normalize">Whether to normalize the score to the range (0, 1)</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="RuntimeError"></exception>
/// <exception cref="NotSupportedException"></exception>
public async Task<IReadOnlyList<float>> GetRelevanceScores(string input, IReadOnlyList<string> documents, bool normalize = false, CancellationToken cancellationToken = default) {
List<float> scores = new List<float>(documents.Count);
foreach (var document in documents)
{
var score = (await GetRelevanceScoreWithTokenCount(input, document, cancellationToken).ConfigureAwait(false)).Score;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This runs the model once for each document. You could probably adapt this to run all at once for every document fairly easily - when you setup the LLamaBatch just use a unique LLamaSeqId for each string.

scores.Add(normalize ? Sigmoid(score) : score);
}
return scores;
}


private async Task<(float Score, int Tokens)> GetRelevanceScoreWithTokenCount(string input, string document, CancellationToken cancellationToken = default)
{
var prompt = $"{input}</s><s>{document}";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be using StrBOS and StrEOS instead of hardcoding them?

// Add all of the tokens to the batch
var tokens = Context.Tokenize(prompt, special: true);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tokenizing the entire prompt with special:true means that characters in the input or document can be interpreted as special tokens. That's probably not what you want!

Would it be better to tokenize input and document separately, and directly insert the EOS/BOS tokens? That also means you don't have to handle EOS/BOS as strings, which is neater.

Something like this:

var inputTokens = Context.Tokenize(input);
var docTokens = Context.Tokenize(document);

var BOS = Context.Vocab.BOS;

var tokens = [..inputTokens, Context.Vocab.EOS, Context.Vocab.BOS, ..docTokens];

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, I will test it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var query = "what is panda?";
var document = "hi";
var tokens1 = reranker.Context.Tokenize($"{query}</s><s>{document}");
var tokens2 = reranker.Context.Tokenize($"{query}</s><s>{document}", special: true);
var eos = reranker.Context.Vocab.EOS!.Value;
var bos = reranker.Context.Vocab.BOS!.Value;
LLamaToken[] tokens3 = [.. reranker.Context.Tokenize(query), eos, bos, .. reranker.Context.Tokenize(document)];
LLamaToken[] tokens4 = [.. reranker.Context.Tokenize(query), .. reranker.Context.Tokenize(document)];
LLamaToken[] tokens5 = [.. reranker.Context.Tokenize(query, special: true), eos, bos, .. reranker.Context.Tokenize(document, special: true)];

Console.WriteLine(string.Join(" ", tokens1));
Console.WriteLine(string.Join(" ", tokens2));
Console.WriteLine(string.Join(" ", tokens3));
Console.WriteLine(string.Join(" ", tokens4));
Console.WriteLine(string.Join(" ", tokens5));
Console.ReadLine();

I haven't fully understood the meaning of special param in the Tokenize func, but the above output results, only tokens2 and tokens4 meet the requirements. So I ended up going with this approach:
var tokens = [..inputTokens, ..docTokens];

In addition, Using llamabatch is 2~ 3 times faster than foreach documents.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The special flag controls how strings like <s> will be parsed into tokens.

With special=false it will be handled as plain text, so the model would see the characters <s> as normal. Just the same as the string "Hello".

With special=true it would be converted into the special BOS token, instead of text.

var batch = new LLamaBatch();
for (var i = 0; i < tokens.Length; i++)
batch.Add(tokens[i], i, LLamaSeqId.Zero, true);

// clear previous kv_cache values
Context.NativeHandle.KvCacheClear();

// Check if we should cancel the work, just before doing anything expensive (encode/decode)
cancellationToken.ThrowIfCancellationRequested();

// Run model
switch (Context.NativeHandle.ModelHandle.HasEncoder, Context.NativeHandle.ModelHandle.HasDecoder)
{
case (true, false):
{
var result = await Context.EncodeAsync(batch, cancellationToken);
if (result != EncodeResult.Ok)
throw new RuntimeError($"Failed to encode: {result}");
break;
}

case (false, true):
{
var result = await Context.DecodeAsync(batch, cancellationToken);
if (result != DecodeResult.Ok)
throw new RuntimeError($"Failed to decode: {result}");
break;
}

default:
throw new NotSupportedException("Unsupported model type");
}

var score = Context.NativeHandle.GetEmbeddingsSeq(LLamaSeqId.Zero)[0];

Context.NativeHandle.KvCacheClear();

return (score, tokens.Length);
}

private float Sigmoid(float x)
{
return (float)(1 / (1 + Math.Exp(-x)));
}
}
18 changes: 12 additions & 6 deletions LLama/Native/SafeLlamaModelHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,18 @@ internal Vocabulary(SafeLlamaModelHandle model)
_model = model;
}

private string? LLamaTokenToString(LLamaToken? token, bool isSpecialToken)
private static LLamaToken? Normalize(LLamaToken token)
{
return token == -1 ? null : token;
}

/// <summary>
/// Translate LLamaToken to String
/// </summary>
/// <param name="token"></param>
/// <param name="isSpecialToken"></param>
/// <returns></returns>
public string? LLamaTokenToString(LLamaToken? token, bool isSpecialToken)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still needed after the latest changes? It looks like it's not used any more

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer needed in llamareranker, but I suggest that this can be opened as a public function

{
if (!token.HasValue)
return null;
Expand All @@ -676,11 +687,6 @@ internal Vocabulary(SafeLlamaModelHandle model)
return Encoding.UTF8.GetStringFromSpan(slice);
}

private static LLamaToken? Normalize(LLamaToken token)
{
return token == -1 ? null : token;
}

/// <summary>
/// Total number of tokens in this vocabulary
/// </summary>
Expand Down