| using System.Collections.Generic; |
| using UnityEngine; |
| using Unity.Sentis; |
| using System.IO; |
| using System.Text; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| public class MiniLM : MonoBehaviour |
| { |
| const BackendType backend = BackendType.GPUCompute; |
|
|
| string string1 = "That is a happy person"; |
|
|
| |
| string string2 = "That is a happy dog"; |
| |
| |
|
|
| |
| const int START_TOKEN = 101; |
| const int END_TOKEN = 102; |
|
|
| Ops ops; |
| ITensorAllocator allocator; |
|
|
| |
| string[] tokens; |
|
|
| IWorker engine; |
|
|
| void Start() |
| { |
| allocator = new TensorCachingAllocator(); |
| ops = WorkerFactory.CreateOps(backend, allocator); |
|
|
| tokens = File.ReadAllLines(Application.streamingAssetsPath + "/vocab.txt"); |
|
|
| Model model = ModelLoader.Load(Application.streamingAssetsPath + "/MiniLMv6.sentis"); |
|
|
| engine = WorkerFactory.CreateWorker(backend, model); |
|
|
| var tokens1 = GetTokens(string1); |
| var tokens2 = GetTokens(string2); |
|
|
| TensorFloat embedding1 = GetEmbedding(tokens1); |
| TensorFloat embedding2 = GetEmbedding(tokens2); |
|
|
| Debug.Log("Similarity Score: " + DotScore(embedding1, embedding2)); |
| } |
|
|
| float DotScore(TensorFloat embedding1, TensorFloat embedding2) |
| { |
| using var prod = ops.Mul(embedding1, embedding2); |
| using var dot = ops.ReduceSum(prod, new int[] { 1 }, false); |
| dot.MakeReadable(); |
| return dot[0]; |
| } |
|
|
| TensorFloat GetEmbedding(List<int> tokens) |
| { |
| int N = tokens.Count; |
| using var input_ids = new TensorInt(new TensorShape(1, N), tokens.ToArray()); |
| using var token_type_ids = new TensorInt(new TensorShape(1, N), new int[N]); |
| int[] mask = new int[N]; |
| for (int i = 0; i < mask.Length; i++) |
| { |
| mask[i] = 1; |
| } |
| using var attention_mask = new TensorInt(new TensorShape(1, N), mask); |
|
|
| var inputs = new Dictionary<string, Tensor> |
| { |
| {"input_ids",input_ids }, |
| {"token_type_ids", token_type_ids}, |
| {"attention_mask", attention_mask } |
| }; |
|
|
| engine.Execute(inputs); |
|
|
| var tokenEmbeddings = engine.PeekOutput("output") as TensorFloat; |
|
|
| return MeanPooling(tokenEmbeddings, attention_mask); |
| } |
|
|
| |
| TensorFloat MeanPooling(TensorFloat tokenEmbeddings, TensorInt attentonMask) |
| { |
| using var mask0 = attentonMask.ShallowReshape(attentonMask.shape.Unsqueeze(-1)) as TensorInt; |
| using var maskExpanded = ops.Expand(mask0, tokenEmbeddings.shape); |
| using var maskExpandedF = ops.Cast(maskExpanded, DataType.Float) as TensorFloat; |
| using var D = ops.Mul(tokenEmbeddings, maskExpandedF); |
| using var A = ops.ReduceSum(D, new[] { 1 }, false); |
| using var C = ops.ReduceSum(maskExpandedF, new[] { 1 }, false); |
| using var B = ops.Clip(C, 1e-9f, float.MaxValue); |
| using var E = ops.Div(A, B); |
| using var F = ops.ReduceL2(E, new[] { 1 }, true); |
| return ops.Div(E, F); |
| } |
|
|
| List<int> GetTokens(string text) |
| { |
| |
| string[] words = text.ToLower().Split(null); |
|
|
| var ids = new List<int> |
| { |
| START_TOKEN |
| }; |
|
|
| string s = ""; |
|
|
| foreach (var word in words) |
| { |
| int start = 0; |
| for(int i = word.Length; i >= 0;i--) |
| { |
| string subword = start == 0 ? word.Substring(start, i) : "##" + word.Substring(start, i-start); |
| int index = System.Array.IndexOf(tokens, subword); |
| if (index >= 0) |
| { |
| ids.Add(index); |
| s += subword + " "; |
| if (i == word.Length) break; |
| start = i; |
| i = word.Length + 1; |
| } |
| } |
| } |
|
|
| ids.Add(END_TOKEN); |
|
|
| Debug.Log("Tokenized sentece = " + s); |
|
|
| return ids; |
| } |
|
|
| private void OnDestroy() |
| { |
| engine?.Dispose(); |
| ops?.Dispose(); |
| allocator?.Dispose(); |
| } |
| } |
|
|