Skip to content

Commit d8f5172

Browse files
authored
Merge pull request #862 from SpaceAntelope/kernel-memory-68-compatibility-fix
Microsoft.KernelMemory version 0.68+ compatibility fix
2 parents 480fc44 + 939d2b1 commit d8f5172

File tree

7 files changed

+225
-26
lines changed

7 files changed

+225
-26
lines changed

LLama.KernelMemory/LLamaSharp.KernelMemory.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
</PropertyGroup>
2828

2929
<ItemGroup>
30-
<PackageReference Include="Microsoft.KernelMemory.Abstractions" Version="0.66.240709.1" />
30+
<PackageReference Include="Microsoft.KernelMemory.Abstractions" Version="0.68.240716.1" />
3131
</ItemGroup>
3232

3333
<ItemGroup>

LLama.KernelMemory/LLamaSharpTextEmbeddingGenerator.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using LLama;
22
using LLama.Common;
3-
using LLama.Native;
43
using Microsoft.KernelMemory;
54
using Microsoft.KernelMemory.AI;
65

@@ -112,5 +111,24 @@ public async Task<Embedding> GenerateEmbeddingAsync(string text, CancellationTok
112111

113112
/// <inheritdoc/>
114113
public int CountTokens(string text) => _embedder.Context.Tokenize(text, special: true).Length;
114+
115+
/// <summary>
116+
/// Get the list of tokens for the input text
117+
/// </summary>
118+
/// <param name="text">Input string to be tokenized</param>
119+
/// <returns>Read-only list of tokens for the input test</returns>
120+
/// <remarks>
121+
/// It throws if text is null and Includes empty stop token because addBos is left true to be consistent with the CountTokens implementation.</remarks>
122+
/// <see cref="CountTokens(string)"/>
123+
public IReadOnlyList<string> GetTokens(string text)
124+
{
125+
/* see relevant unit tests for important implementation notes regarding unicode */
126+
var context = _embedder.Context;
127+
var numericTokens = context.Tokenize(text, special: true);
128+
var decoder = new StreamingTokenDecoder(context);
129+
return numericTokens
130+
.Select(x => { decoder.Add(x); return decoder.Read(); })
131+
.ToList();
132+
}
115133
}
116134
}

LLama.KernelMemory/LlamaSharpTextGenerator.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,23 @@ private static InferenceParams OptionsToParams(TextGenerationOptions options, In
106106

107107
/// <inheritdoc/>
108108
public int CountTokens(string text) => _context.Tokenize(text, special: true).Length;
109+
110+
/// <summary>
111+
/// Get the list of tokens for the input text
112+
/// </summary>
113+
/// <param name="text">Input string to be tokenized</param>
114+
/// <returns>Read-only list of tokens for the input test</returns>
115+
/// <remarks>
116+
/// It throws if text is null and Includes empty stop token because addBos is left true to be consistent with the CountTokens implementation.</remarks>
117+
/// <see cref="CountTokens(string)"/>
118+
public IReadOnlyList<string> GetTokens(string text)
119+
{
120+
/* see relevant unit tests for important implementation notes regarding unicode */
121+
var numericTokens = _context.Tokenize(text, special: true);
122+
var decoder = new StreamingTokenDecoder(_context);
123+
return numericTokens
124+
.Select(x => { decoder.Add(x); return decoder.Read(); })
125+
.ToList();
126+
}
109127
}
110128
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using LLama.Common;
2+
using LLamaSharp.KernelMemory;
3+
using Microsoft.KernelMemory.AI;
4+
using Xunit.Abstractions;
5+
6+
namespace LLama.Unittest.KernelMemory
7+
{
8+
9+
public abstract class ITextTokenizerTests
10+
{
11+
private readonly ITestOutputHelper _testOutputHelper;
12+
13+
#pragma warning disable KMEXP00 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
14+
protected ITextTokenizer? _generator;
15+
#pragma warning restore KMEXP00 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
16+
17+
protected InferenceParams _infParams;
18+
protected LLamaSharpConfig _lsConfig;
19+
20+
public ITextTokenizerTests(ITestOutputHelper testOutputHelper)
21+
{
22+
_testOutputHelper = testOutputHelper;
23+
24+
_infParams = new() { AntiPrompts = ["\n\n"] };
25+
_lsConfig = new(Constants.GenerativeModelPath) { DefaultInferenceParams = _infParams };
26+
27+
testOutputHelper.WriteLine($"Using model {Path.GetFileName(_lsConfig.ModelPath)}");
28+
}
29+
30+
31+
[Theory]
32+
[InlineData("The quick brown fox jumps over the lazy dog")]
33+
[InlineData("Well, here're some special characters!!!")]
34+
[InlineData("...___---")]
35+
[InlineData("15 + 6 = 21 && 68 * 75 = 5100")]
36+
[InlineData(" \n \r\n \t ")]
37+
public void GetTokens_ShouldReturnListOfTokensForInputString(string? text)
38+
{
39+
var tokens = _generator!.GetTokens(text);
40+
var tokensCount = _generator.CountTokens(text);
41+
42+
var expected = " " + text; // the placement of the space corresponding to BOS will vary by model tokenizer
43+
var actual = string.Join("", tokens);
44+
45+
_testOutputHelper.WriteLine($"Tokens for '{text}':");
46+
_testOutputHelper.WriteLine(string.Join("", tokens.Select(x => $"({x})")));
47+
48+
Assert.Equal(expected, actual);
49+
Assert.Equal(tokensCount, tokens.Count);
50+
}
51+
52+
/* This is exactly the same test as the non-unicode cases. However, there are reasons why this
53+
* should be made a special case and may deviate in the future:
54+
*
55+
* As of now there appears to be no final word as to how characters that consist of more than one
56+
* numeric token should correspond to textual tokens, and results vary according to different
57+
* models' tokenizers. For example, given a character 'Z' that corresponds to the numeric tokens {1,2,3}
58+
* some (llama-2) will pad the length of the total number of tokens by returning spaces as tokens
59+
* (i.e. ' ', ' ', 'Z') while others (GPT4Tokenizer) will pad with the character itself (i.e. 'Z','Z','Z').
60+
*
61+
* This is very evident when tokenizing ideograms and emojis, but can arise with various unicode characters
62+
* as well. See pull request for more relevant discussion https://github.com/SciSharp/LLamaSharp/pull/862
63+
*
64+
* Currently the method will remain consistent with the output of ITextTokenizer.CountTokens, meaning
65+
* any redundant tokens will not be omitted as long as they are counted by CountTokens.
66+
*
67+
* StreamingTokenDecoder, while sufficiently useful for this task, was not designed with producing
68+
* output for one numeric token at a time in mind, so ITextTokenizer.GetTokens should not be considered
69+
* an example of proper use.
70+
*
71+
* Note: if this message is removed, also remove references to it in LLamaSharpTextEmbeddingGenerator.GetTokens
72+
* and LLamaSharpTextGenerator.GetTokens
73+
*/
74+
[Theory]
75+
[InlineData("And a little bit of unicode για να κρατήσουμε τα πράγματα ενδιαφέροντα")]
76+
[InlineData("猫坐在垫子上 😀🤨🤐😏")]
77+
public void GetTokens_Unicode_ShouldReturnListOfTokensForInputString(string? text)
78+
{
79+
var tokens = _generator!.GetTokens(text);
80+
var tokensCount = _generator.CountTokens(text);
81+
82+
var expected = " " + text; // the placement of the space corresponding to BOS will vary by model tokenizer
83+
var actual = string.Join("", tokens);
84+
85+
_testOutputHelper.WriteLine($"Tokens for '{text}':");
86+
_testOutputHelper.WriteLine(string.Join("", tokens.Select(x => $"({x})")));
87+
88+
Assert.Equal(expected, actual);
89+
Assert.Equal(tokensCount, tokens.Count);
90+
}
91+
92+
[Fact]
93+
public void GetToken_ShouldThrowForNull()
94+
{
95+
string? text = null;
96+
97+
Assert.Throws<ArgumentNullException>(() => { _generator!.GetTokens(text!); });
98+
}
99+
100+
[Fact]
101+
public void GetToken_EmptyStringYieldsOneEmptyToken()
102+
{
103+
var text = "";
104+
var expected = "";
105+
106+
var tokens = _generator!.GetTokens(text);
107+
var tokensCount = _generator.CountTokens(text);
108+
var actual = tokens.Single();
109+
110+
_testOutputHelper.WriteLine($"Tokens for '{text}':");
111+
_testOutputHelper.WriteLine(string.Join("", tokens.Select(x => $"({x})")));
112+
113+
Assert.Equal(expected, actual);
114+
Assert.Equal(tokensCount, tokens.Count);
115+
}
116+
}
117+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using LLama.Common;
2+
using LLamaSharp.KernelMemory;
3+
using Microsoft.KernelMemory.AI;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Text.RegularExpressions;
9+
using System.Threading.Tasks;
10+
using Xunit.Abstractions;
11+
12+
namespace LLama.Unittest.KernelMemory
13+
{
14+
public class LLamaSharpTextEmbeddingGeneratorTests : ITextTokenizerTests, IDisposable
15+
{
16+
private readonly LLamaSharpTextEmbeddingGenerator _embeddingGenerator;
17+
18+
public LLamaSharpTextEmbeddingGeneratorTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
19+
{
20+
_embeddingGenerator = new LLamaSharpTextEmbeddingGenerator(_lsConfig);
21+
22+
_generator = _embeddingGenerator;
23+
}
24+
25+
public void Dispose()
26+
{
27+
_embeddingGenerator.Dispose();
28+
}
29+
}
30+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using LLama.Common;
2+
using LLamaSharp.KernelMemory;
3+
using Microsoft.KernelMemory.AI;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Linq;
8+
using System.Reflection.Emit;
9+
using System.Text;
10+
using System.Text.RegularExpressions;
11+
using System.Threading.Tasks;
12+
using Xunit.Abstractions;
13+
using Xunit.Sdk;
14+
using static System.Net.Mime.MediaTypeNames;
15+
16+
namespace LLama.Unittest.KernelMemory
17+
{
18+
public class LlamaSharpTextGeneratorTests : ITextTokenizerTests, IDisposable
19+
{
20+
private readonly LlamaSharpTextGenerator _textGenerator;
21+
22+
public LlamaSharpTextGeneratorTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
23+
{
24+
_textGenerator = new LlamaSharpTextGenerator(_lsConfig);
25+
26+
_generator = _textGenerator;
27+
}
28+
29+
public void Dispose()
30+
{
31+
_textGenerator.Dispose();
32+
}
33+
}
34+
}

LLama.Unittest/LLama.Unittest.csproj

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<Import Project="..\LLama\LLamaSharp.Runtime.targets" />
33
<PropertyGroup>
44
<TargetFramework>net8.0</TargetFramework>
@@ -29,31 +29,16 @@
2929

3030
<Target Name="DownloadContentFilesInner">
3131

32-
<DownloadFile
33-
SourceUrl="https://huggingface.co/TheBloke/Llama-2-7b-Chat-GGUF/resolve/main/llama-2-7b-chat.Q3_K_S.gguf"
34-
DestinationFolder="Models"
35-
DestinationFileName="llama-2-7b-chat.Q3_K_S.gguf"
36-
SkipUnchangedFiles="true">
32+
<DownloadFile SourceUrl="https://huggingface.co/TheBloke/Llama-2-7b-Chat-GGUF/resolve/main/llama-2-7b-chat.Q3_K_S.gguf" DestinationFolder="Models" DestinationFileName="llama-2-7b-chat.Q3_K_S.gguf" SkipUnchangedFiles="true">
3733
</DownloadFile>
3834

39-
<DownloadFile
40-
SourceUrl="https://huggingface.co/cjpais/llava-1.6-mistral-7b-gguf/resolve/main/llava-v1.6-mistral-7b.Q3_K_XS.gguf"
41-
DestinationFolder="Models" DestinationFileName="llava-v1.6-mistral-7b.Q3_K_XS.gguf"
42-
SkipUnchangedFiles="true">
35+
<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">
4336
</DownloadFile>
4437

45-
<DownloadFile
46-
SourceUrl="https://huggingface.co/cjpais/llava-1.6-mistral-7b-gguf/resolve/main/mmproj-model-f16.gguf"
47-
DestinationFolder="Models"
48-
DestinationFileName="mmproj-model-f16.gguf"
49-
SkipUnchangedFiles="true">
38+
<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">
5039
</DownloadFile>
5140

52-
<DownloadFile
53-
SourceUrl="https://huggingface.co/leliuga/all-MiniLM-L12-v2-GGUF/resolve/main/all-MiniLM-L12-v2.Q8_0.gguf"
54-
DestinationFolder="Models"
55-
DestinationFileName="all-MiniLM-L12-v2.Q8_0.gguf"
56-
SkipUnchangedFiles="true">
41+
<DownloadFile SourceUrl="https://huggingface.co/leliuga/all-MiniLM-L12-v2-GGUF/resolve/main/all-MiniLM-L12-v2.Q8_0.gguf" DestinationFolder="Models" DestinationFileName="all-MiniLM-L12-v2.Q8_0.gguf" SkipUnchangedFiles="true">
5742
</DownloadFile>
5843

5944
</Target>
@@ -63,14 +48,11 @@
6348
</Target>
6449

6550
<ItemGroup>
51+
<ProjectReference Include="..\LLama.KernelMemory\LLamaSharp.KernelMemory.csproj" />
6652
<ProjectReference Include="..\LLama.SemanticKernel\LLamaSharp.SemanticKernel.csproj" />
6753
<ProjectReference Include="..\LLama\LLamaSharp.csproj" />
6854
</ItemGroup>
6955

70-
<ItemGroup>
71-
<Folder Include="Models\" />
72-
</ItemGroup>
73-
7456
<ItemGroup>
7557
<None Update="Models\all-MiniLM-L12-v2.Q8_0.gguf">
7658
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

0 commit comments

Comments
 (0)