Für meinen Anwendungsfall, vorhandene Issue Einträge zu vergleichen, will ich nun auf eine in Memory Lösung wechseln. Da das wesentlich komplexer ist, trenne ich die Blogartikel und fokussiere mich auf das Tokenize des Textes. Das ist der Vorgang der ein Dokument, Satz und sogar Wort so zerlegt das ein Computer eine Zahl sieht. Oftmals ist ein Token auch die Verrechnungseinheit für den Service.
Ollama hatte aus dem Nomic Text Modell alles nötige extrahiert und uns eine API zur Verfügung gestellt. Ohne Api müssen wir nun vieles per Hand machen. Zunächst die Auswahl des Modells. Nein, noch früher den Typ. Microsoft favorisiert ONNX. Wie man sieht, gibt es darunter wieder verschiedene Varianten. Beim konvertieren legt irgendwer fest, mit welchen Datentypen und Optimierungen so ein Modell dann funktionieren soll. Ich wähle model_q4f16.onnx weil es mir als das kleinste erscheint.
Das Modell wurde quantisiert, sodass: Gewichte nur 4 Bit brauchen (int4) Aktivierungen oder Eingaben in float16 bleiben. Das spart Platz und Rechenleistung – vor allem bei Inference auf CPUs oder GPUs.
Auf Huggingface finden sich auch zwei weitere u.U. wichtige Dateien. Die Infos sind zwar im Modell eingebettet, aber manchmal kann das SDK das nicht direkt extrahieren. Vocab.txt werden wir tatsächlich benötigen. Tokenizer.json beinhaltet auch vocab und mehr Infos. Für uns wird der Eintrag Model Type relevant (WordPiece). Auszug
"model": {
"type": "WordPiece",
"unk_token": "[UNK]",
"continuing_subword_prefix": "##",
"max_input_chars_per_word": 100,
"vocab": {
"[PAD]": 0,
"[unused0]": 1,
"[unused1]": 2,
Dann lohnt sich noch ein Blick in das Modell mit
Netron. Wir sehen das Modell braucht zwei Input Objekte

Und diese Input Parameter (Tensor genannt) bestehen aus drei weiteren Parametern, die gerne Int64 typisiert wären. Das .NET äquivalent ist long.

Für mein Coding brauche ich die NUget Pakete Microsoft.ML.OnnxRuntime und Microsoft.ML.Tokenizers. Der WordPiece Tokenizer, obwohl nicht neu, scheint nur in den neuesten Preview Paket enthalten zu sein.
The WordPiece tokenizer is a sub-word tokenizer that is used in BERT and other transformer models.
Wie vorhin ausgeführt werden drei Konfigurationsobjekte benötigt. Beginnend mit WordPieceOptions. Es müsste reichen einfach ein Objekt zu erstellen, weil die Werte in Zeile 2/3 die Default Werte sind. Soweit der Source Code auf Gthub erzählt.
1: var o = new WordPieceOptions();
2: o.UnknownToken = "[UNK]";
3: o.ContinuingSubwordPrefix = "##";
Weiter wird der Tokenizer mit dem Vocab.txt erzeugt (Download von Huggingface), mit den Parametern von eben.
Im voraus gehenden Blogartikel unter Einsatz von Ollama, wurde der Text aus Zeile 2 vektorisiert. Dabei entstand ein Vektor in der Dimension 768.
1: var tokenizer = WordPieceTokenizer.Create("Models/vocab.txt", o);
2: var encoding = tokenizer.EncodeToIds("Netzwerk / Internet Wien");
3: var inputIds = encoding.Select(id => (long)id).ToArray();
Folgender Code ist außerhalb der Programmierreihenfolge und zeigt die drei Parameter. Bisher haben wir erst das Objekt InputIDs
1: var inputs = new List<NamedOnnxValue>
2: {
3: NamedOnnxValue.CreateFromTensor("input_ids",
new DenseTensor<long>(inputIds, new[] { 1, length })),
4: NamedOnnxValue.CreateFromTensor("attention_mask",
new DenseTensor<long>(attentionMask, new[] { 1, length })),
5: NamedOnnxValue.CreateFromTensor("token_type_ids",
new DenseTensor<long>(tokenTypeIds, new[] { 1, length }))
6: };
Also muss vorher noch Code eingefügt werden. Das eine einfach 0 und das andere eine Maske ob ein Token berücksichtigt werden soll (0/1)
1: var length = inputIds.Length;
2: var tokenTypeIds = new long[length]; // alle Werte = 0
3: var attentionMask = Enumerable.Repeat(1L, length).ToArray();
4:
Nach all der Arbeit kann das Modell final geladen und befragt werden. Dafür stellt Microsoft.ML die InferenceSession bereit.
1: using var session = new InferenceSession(modelPath);
2: using var results = session.Run(inputs);
3: var output = results.First().AsTensor<float>().ToArray();
Das Problem: Mein Vektor hat nun 3072 Dimensionen und kann nicht mit den per Olama Api in der Datenbank generierten Werten verglichen werden.

Am einfachsten ist natürlich einfach die Tabelle neu zu füllen. Falls wer das tiefe Verständnis hat, welcher Parameter und welche Modell Variante aus dem ONNX Stall mit idente Vektoren bringt, dann bitte melden.
UPDATE: die Lösung Mean Pooling