mirror of
https://github.com/MAKS-IT-COM/maksit-postclient.git
synced 2026-11-13 21:56:11 +01:00
194 lines
6.2 KiB
C#
194 lines
6.2 KiB
C#
using System.Reflection;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using Microsoft.ML.Tokenizers;
|
|
using Microsoft.ML.OnnxRuntime;
|
|
using Microsoft.ML.OnnxRuntime.Tensors;
|
|
using MaksIT.PostClient.Shared;
|
|
|
|
|
|
namespace MaksIT.PostClient.Client;
|
|
|
|
|
|
public sealed class OnnxGemmaEmbedder : ITextEmbedder {
|
|
private readonly InferenceSession _session;
|
|
private readonly Tokenizer _tokenizer;
|
|
private readonly string _inputIds;
|
|
private readonly string _attentionMask;
|
|
private readonly string? _tokenTypeIds;
|
|
private readonly string _output;
|
|
private readonly bool _outputIsTokens;
|
|
private readonly Lock _gate = new();
|
|
|
|
public OnnxGemmaEmbedder(string onnxPath, string tokenizerPath, bool useGpu) {
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(onnxPath);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(tokenizerPath);
|
|
using var stream = File.OpenRead(tokenizerPath);
|
|
_tokenizer = LlamaTokenizer.Create(stream, addBeginOfSentence: true, addEndOfSentence: true);
|
|
UsesGpu = false;
|
|
if (useGpu && TryCreateGpuSession(onnxPath, out var gpu)) {
|
|
_session = gpu;
|
|
UsesGpu = true;
|
|
}
|
|
else {
|
|
_session = CreateCpuSession(onnxPath);
|
|
}
|
|
_inputIds = PickInput(_session, "input_ids", "input");
|
|
_attentionMask = PickInput(_session, "attention_mask", "mask");
|
|
_tokenTypeIds = FindInput(_session, "token_type_ids");
|
|
_output = PickOutput(_session);
|
|
_outputIsTokens = _session.OutputMetadata[_output].Dimensions.Length == 3;
|
|
}
|
|
|
|
public bool IsReady =>
|
|
true;
|
|
|
|
public bool UsesGpu { get; }
|
|
|
|
public string ModelId =>
|
|
EmbeddingModelSpec.Id;
|
|
|
|
public float[] Embed(string text) {
|
|
var ids = _tokenizer.EncodeToIds(text ?? "");
|
|
if (ids.Count == 0)
|
|
ids = [0];
|
|
if (ids.Count > EmbeddingModelSpec.MaxTokens)
|
|
ids = ids.Take(EmbeddingModelSpec.MaxTokens).ToList();
|
|
var length = ids.Count;
|
|
var inputIds = new DenseTensor<long>(new[] { 1, length });
|
|
var mask = new DenseTensor<long>(new[] { 1, length });
|
|
for (var i = 0; i < length; i++) {
|
|
inputIds[0, i] = ids[i];
|
|
mask[0, i] = 1;
|
|
}
|
|
|
|
var inputs = new List<NamedOnnxValue> {
|
|
NamedOnnxValue.CreateFromTensor(_inputIds, inputIds),
|
|
NamedOnnxValue.CreateFromTensor(_attentionMask, mask)
|
|
};
|
|
if (_tokenTypeIds is not null) {
|
|
var types = new DenseTensor<long>(new[] { 1, length });
|
|
inputs.Add(NamedOnnxValue.CreateFromTensor(_tokenTypeIds, types));
|
|
}
|
|
|
|
IDisposableReadOnlyCollection<DisposableNamedOnnxValue> results;
|
|
lock (_gate)
|
|
results = _session.Run(inputs);
|
|
using (results) {
|
|
var tensor = results.First(r => r.Name == _output).AsTensor<float>();
|
|
var vector = _outputIsTokens ? MeanPool(tensor, length) : Row(tensor);
|
|
return EmbeddingVector.Truncate(vector, EmbeddingModelSpec.StoredDimensions);
|
|
}
|
|
}
|
|
|
|
public void Dispose() =>
|
|
_session.Dispose();
|
|
|
|
private static bool TryCreateGpuSession(string onnxPath, [NotNullWhen(true)] out InferenceSession? session) {
|
|
session = null;
|
|
var method = typeof(SessionOptions).GetMethod("AppendExecutionProvider_DML", [typeof(int)]);
|
|
if (method is null)
|
|
return false;
|
|
|
|
Exception? last = null;
|
|
foreach (var adapter in GpuProbe.DirectMlAdapters()) {
|
|
var options = DmlSessionOptions();
|
|
try {
|
|
method.Invoke(options, [adapter.DeviceId]);
|
|
TryAppendCpu(options);
|
|
session = new InferenceSession(onnxPath, options);
|
|
AppLog.Write($"Meaning index DirectML: {adapter.Name} (device {adapter.DeviceId})");
|
|
return true;
|
|
}
|
|
catch (Exception ex) {
|
|
options.Dispose();
|
|
last = Unwrap(ex);
|
|
AppLog.Write($"Meaning index DirectML skipped {adapter.Name} (device {adapter.DeviceId}): {last.Message}");
|
|
}
|
|
}
|
|
|
|
if (last is not null)
|
|
throw last;
|
|
return false;
|
|
}
|
|
|
|
private static InferenceSession CreateCpuSession(string onnxPath) {
|
|
var options = new SessionOptions();
|
|
TryAppendCpu(options);
|
|
return new InferenceSession(onnxPath, options);
|
|
}
|
|
|
|
private static SessionOptions DmlSessionOptions() =>
|
|
new() {
|
|
GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL,
|
|
ExecutionMode = ExecutionMode.ORT_SEQUENTIAL,
|
|
EnableMemoryPattern = false,
|
|
EnableCpuMemArena = false
|
|
};
|
|
|
|
private static void TryAppendCpu(SessionOptions options) {
|
|
try {
|
|
options.AppendExecutionProvider_CPU();
|
|
}
|
|
catch {
|
|
}
|
|
}
|
|
|
|
private static Exception Unwrap(Exception exception) =>
|
|
exception is TargetInvocationException { InnerException: { } inner } ? inner : exception;
|
|
|
|
private static string PickInput(InferenceSession session, params string[] names) {
|
|
foreach (var name in names) {
|
|
foreach (var key in session.InputMetadata.Keys) {
|
|
if (key.Equals(name, StringComparison.OrdinalIgnoreCase) || key.Contains(name, StringComparison.OrdinalIgnoreCase))
|
|
return key;
|
|
}
|
|
}
|
|
|
|
return session.InputMetadata.Keys.First();
|
|
}
|
|
|
|
private static string? FindInput(InferenceSession session, string name) {
|
|
foreach (var key in session.InputMetadata.Keys) {
|
|
if (key.Equals(name, StringComparison.OrdinalIgnoreCase))
|
|
return key;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string PickOutput(InferenceSession session) {
|
|
foreach (var key in session.OutputMetadata.Keys) {
|
|
if (key.Contains("sentence", StringComparison.OrdinalIgnoreCase)
|
|
|| key.Contains("pool", StringComparison.OrdinalIgnoreCase)
|
|
|| key.Equals("embeddings", StringComparison.OrdinalIgnoreCase))
|
|
return key;
|
|
}
|
|
|
|
return session.OutputMetadata.Keys.Last();
|
|
}
|
|
|
|
private static float[] MeanPool(Tensor<float> tensor, int length) {
|
|
var dims = tensor.Dimensions;
|
|
var hidden = dims.Length >= 3 ? dims[2] : dims[1];
|
|
var sum = new float[hidden];
|
|
for (var i = 0; i < length; i++) {
|
|
for (var h = 0; h < hidden; h++)
|
|
sum[h] += tensor[0, i, h];
|
|
}
|
|
|
|
var scale = 1f / Math.Max(length, 1);
|
|
for (var h = 0; h < hidden; h++)
|
|
sum[h] *= scale;
|
|
return sum;
|
|
}
|
|
|
|
private static float[] Row(Tensor<float> tensor) {
|
|
var dims = tensor.Dimensions;
|
|
var hidden = dims.Length == 1 ? dims[0] : dims[^1];
|
|
var values = new float[hidden];
|
|
for (var i = 0; i < hidden; i++)
|
|
values[i] = dims.Length == 1 ? tensor[i] : tensor[0, i];
|
|
return values;
|
|
}
|
|
}
|