LLM for Unity  v2.4.1
Create characters in Unity with LLMs!
Loading...
Searching...
No Matches
LLMUnitySetup.cs
Go to the documentation of this file.
1
3using UnityEditor;
4using System.IO;
5using UnityEngine;
6using System.Threading.Tasks;
7using System;
8using System.IO.Compression;
9using System.Collections.Generic;
10using UnityEngine.Networking;
11using System.Text.RegularExpressions;
12
16namespace LLMUnity
17{
19 public sealed class FloatAttribute : PropertyAttribute
20 {
21 public float Min { get; private set; }
22 public float Max { get; private set; }
23
24 public FloatAttribute(float min, float max)
25 {
26 Min = min;
27 Max = max;
28 }
29 }
30 public sealed class IntAttribute : PropertyAttribute
31 {
32 public int Min { get; private set; }
33 public int Max { get; private set; }
34
35 public IntAttribute(int min, int max)
36 {
37 Min = min;
38 Max = max;
39 }
40 }
41
42 [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
43 public class DynamicRangeAttribute : PropertyAttribute
44 {
45 public readonly string minVariable;
46 public readonly string maxVariable;
47 public bool intOrFloat;
48
49 public DynamicRangeAttribute(string minVariable, string maxVariable, bool intOrFloat)
50 {
51 this.minVariable = minVariable;
52 this.maxVariable = maxVariable;
53 this.intOrFloat = intOrFloat;
54 }
55 }
56
57 public class LLMAttribute : PropertyAttribute {}
58 public class LocalRemoteAttribute : PropertyAttribute {}
59 public class RemoteAttribute : PropertyAttribute {}
60 public class LocalAttribute : PropertyAttribute {}
61 public class ModelAttribute : PropertyAttribute {}
62 public class ModelExtrasAttribute : PropertyAttribute {}
63 public class ChatAttribute : PropertyAttribute {}
64 public class LLMUnityAttribute : PropertyAttribute {}
65
66 public class AdvancedAttribute : PropertyAttribute {}
67 public class LLMAdvancedAttribute : AdvancedAttribute {}
68 public class ModelAdvancedAttribute : AdvancedAttribute {}
69 public class ChatAdvancedAttribute : AdvancedAttribute {}
70
71 public class NotImplementedException : Exception
72 {
73 public NotImplementedException() : base("The method needs to be implemented by subclasses.") {}
74 }
75
76 public delegate void EmptyCallback();
77 public delegate void Callback<T>(T message);
78 public delegate Task TaskCallback<T>(T message);
79 public delegate T2 ContentCallback<T, T2>(T message);
80 public delegate void ActionCallback(string source, string target);
81
82 [Serializable]
83 public struct StringPair
84 {
85 public string source;
86 public string target;
87 }
88
89 [Serializable]
90 public class ListStringPair
91 {
92 public List<StringPair> pairs;
93 }
95
100 public class LLMUnitySetup
101 {
102 // DON'T CHANGE! the version is autocompleted with a GitHub action
104 public static string Version = "v2.4.1";
106 public static string LlamaLibVersion = "v1.2.1";
108 public static string LlamaLibReleaseURL = $"https://github.com/undreamai/LlamaLib/releases/download/{LlamaLibVersion}";
110 public static string libraryName = GetLibraryName(LlamaLibVersion);
112 public static string libraryPath = GetAssetPath(libraryName);
114 public static string LlamaLibURL = $"{LlamaLibReleaseURL}/{libraryName}.zip";
116 public static string LlamaLibExtensionURL = $"{LlamaLibReleaseURL}/{libraryName}-full.zip";
118 public static string LLMUnityStore = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "LLMUnity");
120 public static string modelDownloadPath = Path.Combine(LLMUnityStore, "models");
122 public static string LLMManagerPath = GetAssetPath("LLMManager.json");
123
125 [HideInInspector] public static readonly Dictionary<string, (string, string, string)[]> modelOptions = new Dictionary<string, (string, string, string)[]>()
126 {
127 {"Medium models", new(string, string, string)[]
128 {
129 ("Llama 3.1 8B", "https://huggingface.co/bartowski/Meta-Llama-3.1-8B-Instruct-GGUF/resolve/main/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf?download=true", "https://huggingface.co/meta-llama/Meta-Llama-3.1-8B/blob/main/LICENSE"),
130 ("Mistral 7B Instruct v0.2", "https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q4_K_M.gguf?download=true", null),
131 ("Gemma 2 9B it", "https://huggingface.co/bartowski/gemma-2-9b-it-GGUF/resolve/main/gemma-2-9b-it-Q4_K_M.gguf?download=true", "https://ai.google.dev/gemma/terms"),
132 ("OpenHermes 2.5 7B", "https://huggingface.co/TheBloke/OpenHermes-2.5-Mistral-7B-GGUF/resolve/main/openhermes-2.5-mistral-7b.Q4_K_M.gguf?download=true", null),
133 }},
134 {"Small models", new(string, string, string)[]
135 {
136 ("Llama 3.2 3B", "https://huggingface.co/hugging-quants/Llama-3.2-3B-Instruct-Q4_K_M-GGUF/resolve/main/llama-3.2-3b-instruct-q4_k_m.gguf", null),
137 ("Phi 3.5 4B", "https://huggingface.co/bartowski/Phi-3.5-mini-instruct-GGUF/resolve/main/Phi-3.5-mini-instruct-Q4_K_M.gguf", null),
138 }},
139 {"Tiny models", new(string, string, string)[]
140 {
141 ("Llama 3.2 1B", "https://huggingface.co/hugging-quants/Llama-3.2-1B-Instruct-Q4_K_M-GGUF/resolve/main/llama-3.2-1b-instruct-q4_k_m.gguf", null),
142 ("Qwen 2 0.5B", "https://huggingface.co/Qwen/Qwen2-0.5B-Instruct-GGUF/resolve/main/qwen2-0_5b-instruct-q4_k_m.gguf?download=true", null),
143 }},
144 {"RAG models", new(string, string, string)[]
145 {
146 ("All MiniLM L12 v2", "https://huggingface.co/leliuga/all-MiniLM-L12-v2-GGUF/resolve/main/all-MiniLM-L12-v2.Q4_K_M.gguf", null),
147 ("BGE large en v1.5", "https://huggingface.co/CompendiumLabs/bge-large-en-v1.5-gguf/resolve/main/bge-large-en-v1.5-q4_k_m.gguf", null),
148 ("BGE base en v1.5", "https://huggingface.co/CompendiumLabs/bge-base-en-v1.5-gguf/resolve/main/bge-base-en-v1.5-q4_k_m.gguf", null),
149 ("BGE small en v1.5", "https://huggingface.co/CompendiumLabs/bge-small-en-v1.5-gguf/resolve/main/bge-small-en-v1.5-q4_k_m.gguf", null),
150 }},
151 };
152
154 [LLMUnity] public static DebugModeType DebugMode = DebugModeType.All;
155 static string DebugModeKey = "DebugMode";
156 public static bool FullLlamaLib = false;
157 static string FullLlamaLibKey = "FullLlamaLib";
158 static List<Callback<string>> errorCallbacks = new List<Callback<string>>();
159 static readonly object lockObject = new object();
160 static Dictionary<string, Task> androidExtractTasks = new Dictionary<string, Task>();
161
162 public enum DebugModeType
163 {
164 All,
165 Warning,
166 Error,
167 None
168 }
169
170 public static void Log(string message)
171 {
172 if ((int)DebugMode > (int)DebugModeType.All) return;
173 Debug.Log(message);
174 }
175
176 public static void LogWarning(string message)
177 {
178 if ((int)DebugMode > (int)DebugModeType.Warning) return;
179 Debug.LogWarning(message);
180 }
181
182 public static void LogError(string message)
183 {
184 if ((int)DebugMode > (int)DebugModeType.Error) return;
185 Debug.LogError(message);
186 foreach (Callback<string> errorCallback in errorCallbacks) errorCallback(message);
187 }
188
189 static void LoadPlayerPrefs()
190 {
191 DebugMode = (DebugModeType)PlayerPrefs.GetInt(DebugModeKey, (int)DebugModeType.All);
192 FullLlamaLib = PlayerPrefs.GetInt(FullLlamaLibKey, 0) == 1;
193 }
194
195 public static void SetDebugMode(DebugModeType newDebugMode)
196 {
197 if (DebugMode == newDebugMode) return;
198 DebugMode = newDebugMode;
199 PlayerPrefs.SetInt(DebugModeKey, (int)DebugMode);
200 PlayerPrefs.Save();
201 }
202
203#if UNITY_EDITOR
204 public static void SetFullLlamaLib(bool value)
205 {
206 if (FullLlamaLib == value) return;
207 FullLlamaLib = value;
208 PlayerPrefs.SetInt(FullLlamaLibKey, value ? 1 : 0);
209 PlayerPrefs.Save();
210 _ = DownloadLibrary();
211 }
212
213#endif
214
215 public static string GetLibraryName(string version)
216 {
217 return $"undreamai-{version}-llamacpp";
218 }
219
220 public static string GetAssetPath(string relPath = "")
221 {
222 string assetsDir = Application.platform == RuntimePlatform.Android? Application.persistentDataPath : Application.streamingAssetsPath;
223 return Path.Combine(assetsDir, relPath).Replace('\\', '/');
224 }
225
226 public static string GetDownloadAssetPath(string relPath = "")
227 {
228 string assetsDir = (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer)? Application.persistentDataPath : Application.streamingAssetsPath;
229 return Path.Combine(assetsDir, relPath).Replace('\\', '/');
230 }
231
232#if UNITY_EDITOR
233 [InitializeOnLoadMethod]
234 static async Task InitializeOnLoad()
235 {
236 LoadPlayerPrefs();
237 await DownloadLibrary();
238 }
239
240#else
241 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
242 void InitializeOnLoad()
243 {
244 LoadPlayerPrefs();
245 }
246
247#endif
248
249 static Dictionary<string, ResumingWebClient> downloadClients = new Dictionary<string, ResumingWebClient>();
250
251 public static void CancelDownload(string savePath)
252 {
253 if (!downloadClients.ContainsKey(savePath)) return;
254 downloadClients[savePath].CancelDownloadAsync();
255 downloadClients.Remove(savePath);
256 }
257
258 public static async Task DownloadFile(
259 string fileUrl, string savePath, bool overwrite = false,
260 Callback<string> callback = null, Callback<float> progressCallback = null
261 )
262 {
263 if (File.Exists(savePath) && !overwrite)
264 {
265 Log($"File already exists at: {savePath}");
266 }
267 else
268 {
269 Log($"Downloading {fileUrl} to {savePath}...");
270 string tmpPath = Path.Combine(Application.temporaryCachePath, Path.GetFileName(savePath));
271
272 ResumingWebClient client = new ResumingWebClient();
273 downloadClients[savePath] = client;
274 await client.DownloadFileTaskAsyncResume(new Uri(fileUrl), tmpPath, !overwrite, progressCallback);
275 downloadClients.Remove(savePath);
276#if UNITY_EDITOR
277 AssetDatabase.StartAssetEditing();
278#endif
279 Directory.CreateDirectory(Path.GetDirectoryName(savePath));
280 File.Move(tmpPath, savePath);
281#if UNITY_EDITOR
282 AssetDatabase.StopAssetEditing();
283#endif
284 Log($"Download complete!");
285 }
286
287 progressCallback?.Invoke(1f);
288 callback?.Invoke(savePath);
289 }
290
291 public static async Task AndroidExtractFile(string assetName, bool overwrite = false, bool log = true, int chunkSize = 1024*1024)
292 {
293 Task extractionTask;
294 lock (lockObject)
295 {
296 if (!androidExtractTasks.TryGetValue(assetName, out extractionTask))
297 {
298#if UNITY_ANDROID
299 extractionTask = AndroidExtractFileOnce(assetName, overwrite, log, chunkSize);
300#else
301 extractionTask = Task.CompletedTask;
302#endif
303 androidExtractTasks[assetName] = extractionTask;
304 }
305 }
306 await extractionTask;
307 }
308
309 public static async Task AndroidExtractFileOnce(string assetName, bool overwrite = false, bool log = true, int chunkSize = 1024*1024)
310 {
311 string source = "jar:file://" + Application.dataPath + "!/assets/" + assetName;
312 string target = GetAssetPath(assetName);
313 if (!overwrite && File.Exists(target))
314 {
315 if (log) Log($"File {target} already exists");
316 return;
317 }
318
319 Log($"Extracting {source} to {target}");
320
321 // UnityWebRequest to read the file from StreamingAssets
322 UnityWebRequest www = UnityWebRequest.Get(source);
323 // Send the request and await its completion
324 var operation = www.SendWebRequest();
325
326 while (!operation.isDone) await Task.Delay(1);
327 if (www.result != UnityWebRequest.Result.Success)
328 {
329 LogError("Failed to load file from StreamingAssets: " + www.error);
330 }
331 else
332 {
333 byte[] buffer = new byte[chunkSize];
334 using (Stream responseStream = new MemoryStream(www.downloadHandler.data))
335 using (FileStream fileStream = new FileStream(target, FileMode.Create, FileAccess.Write))
336 {
337 int bytesRead;
338 while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
339 {
340 await fileStream.WriteAsync(buffer, 0, bytesRead);
341 }
342 }
343 }
344 }
345
346 public static async Task AndroidExtractAsset(string path, bool overwrite = false)
347 {
348 if (Application.platform != RuntimePlatform.Android) return;
349 await AndroidExtractFile(Path.GetFileName(path), overwrite);
350 }
351
352 public static string GetFullPath(string path)
353 {
354 return Path.GetFullPath(path).Replace('\\', '/');
355 }
356
357 public static bool IsSubPath(string childPath, string parentPath)
358 {
359 return GetFullPath(childPath).StartsWith(GetFullPath(parentPath), StringComparison.OrdinalIgnoreCase);
360 }
361
362 public static string RelativePath(string fullPath, string basePath)
363 {
364 // Get the full paths and replace backslashes with forward slashes (or vice versa)
365 string fullParentPath = GetFullPath(basePath).TrimEnd('/');
366 string fullChildPath = GetFullPath(fullPath);
367
368 string relativePath = fullChildPath;
369 if (fullChildPath.StartsWith(fullParentPath, StringComparison.OrdinalIgnoreCase))
370 {
371 relativePath = fullChildPath.Substring(fullParentPath.Length);
372 while (relativePath.StartsWith("/")) relativePath = relativePath.Substring(1);
373 }
374 return relativePath;
375 }
376
377#if UNITY_EDITOR
378
379 [HideInInspector] public static float libraryProgress = 1;
380
381 public static void CreateEmptyFile(string path)
382 {
383 File.Create(path).Dispose();
384 }
385
386 static void ExtractInsideDirectory(string zipPath, string extractPath, bool overwrite = true)
387 {
388 using (ZipArchive archive = ZipFile.OpenRead(zipPath))
389 {
390 foreach (ZipArchiveEntry entry in archive.Entries)
391 {
392 if (string.IsNullOrEmpty(entry.Name)) continue;
393 string destinationPath = Path.Combine(extractPath, entry.FullName);
394 Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
395 entry.ExtractToFile(destinationPath, overwrite);
396 }
397 }
398 }
399
400 static async Task DownloadAndExtractInsideDirectory(string url, string path, string setupDir)
401 {
402 string urlName = Path.GetFileName(url);
403 string setupFile = Path.Combine(setupDir, urlName + ".complete");
404 if (File.Exists(setupFile)) return;
405
406 string zipPath = Path.Combine(Application.temporaryCachePath, urlName);
407 await DownloadFile(url, zipPath, true, null, SetLibraryProgress);
408
409 AssetDatabase.StartAssetEditing();
410 ExtractInsideDirectory(zipPath, path);
411 CreateEmptyFile(setupFile);
412 AssetDatabase.StopAssetEditing();
413
414 File.Delete(zipPath);
415 }
416
417
418 static void DeleteEarlierVersions()
419 {
420 List<string> assetPathSubDirs = new List<string>();
421 foreach (string dir in new string[]{GetAssetPath(), Path.Combine(Application.dataPath, "Plugins", "Android")})
422 {
423 if(Directory.Exists(dir)) assetPathSubDirs.AddRange(Directory.GetDirectories(dir));
424 }
425
426 Regex regex = new Regex(GetLibraryName("(.+)"));
427 foreach (string assetPathSubDir in assetPathSubDirs)
428 {
429 Match match = regex.Match(Path.GetFileName(assetPathSubDir));
430 if (match.Success)
431 {
432 string version = match.Groups[1].Value;
433 if (version != LlamaLibVersion)
434 {
435 Debug.Log($"Deleting other LLMUnity version folder: {assetPathSubDir}");
436 Directory.Delete(assetPathSubDir, true);
437 if (File.Exists(assetPathSubDir + ".meta")) File.Delete(assetPathSubDir + ".meta");
438 }
439 }
440 }
441 }
442
443 static async Task DownloadLibrary()
444 {
445 if (libraryProgress < 1) return;
446 libraryProgress = 0;
447
448 try
449 {
450 DeleteEarlierVersions();
451
452 string setupDir = Path.Combine(libraryPath, "setup");
453 Directory.CreateDirectory(setupDir);
454
455 // setup LlamaLib in StreamingAssets
456 await DownloadAndExtractInsideDirectory(LlamaLibURL, libraryPath, setupDir);
457
458 // setup LlamaLib extras in StreamingAssets
459 if (FullLlamaLib) await DownloadAndExtractInsideDirectory(LlamaLibExtensionURL, libraryPath, setupDir);
460 }
461 catch (Exception e)
462 {
463 LogError(e.Message);
464 }
465
466 libraryProgress = 1;
467 }
468
469 private static void SetLibraryProgress(float progress)
470 {
471 libraryProgress = Math.Min(0.99f, progress);
472 }
473
474 public static string AddAsset(string assetPath)
475 {
476 if (!File.Exists(assetPath))
477 {
478 LogError($"{assetPath} does not exist!");
479 return null;
480 }
481 string assetDir = GetAssetPath();
482 if (IsSubPath(assetPath, assetDir)) return RelativePath(assetPath, assetDir);
483
484 string filename = Path.GetFileName(assetPath);
485 string fullPath = GetAssetPath(filename);
486 AssetDatabase.StartAssetEditing();
487 foreach (string path in new string[] {fullPath, fullPath + ".meta"})
488 {
489 if (File.Exists(path)) File.Delete(path);
490 }
491 File.Copy(assetPath, fullPath);
492 AssetDatabase.StopAssetEditing();
493 return filename;
494 }
495
496#endif
498
500 public static void AddErrorCallBack(Callback<string> callback)
501 {
502 errorCallbacks.Add(callback);
503 }
504
506 public static void RemoveErrorCallBack(Callback<string> callback)
507 {
508 errorCallbacks.Remove(callback);
509 }
510
512 public static void ClearErrorCallBacks()
513 {
514 errorCallbacks.Clear();
515 }
516
517 public static int GetMaxFreqKHz(int cpuId)
518 {
519 string[] paths = new string[]
520 {
521 $"/sys/devices/system/cpu/cpufreq/stats/cpu{cpuId}/time_in_state",
522 $"/sys/devices/system/cpu/cpu{cpuId}/cpufreq/stats/time_in_state",
523 $"/sys/devices/system/cpu/cpu{cpuId}/cpufreq/cpuinfo_max_freq"
524 };
525
526 foreach (var path in paths)
527 {
528 if (!File.Exists(path)) continue;
529
530 int maxFreqKHz = 0;
531 using (StreamReader sr = new StreamReader(path))
532 {
533 string line;
534 while ((line = sr.ReadLine()) != null)
535 {
536 string[] parts = line.Split(' ');
537 if (parts.Length > 0 && int.TryParse(parts[0], out int freqKHz))
538 {
539 if (freqKHz > maxFreqKHz)
540 {
541 maxFreqKHz = freqKHz;
542 }
543 }
544 }
545 }
546 if (maxFreqKHz != 0) return maxFreqKHz;
547 }
548 return -1;
549 }
550
551 public static bool IsSmtCpu(int cpuId)
552 {
553 string[] paths = new string[]
554 {
555 $"/sys/devices/system/cpu/cpu{cpuId}/topology/core_cpus_list",
556 $"/sys/devices/system/cpu/cpu{cpuId}/topology/thread_siblings_list"
557 };
558
559 foreach (var path in paths)
560 {
561 if (!File.Exists(path)) continue;
562 using (StreamReader sr = new StreamReader(path))
563 {
564 string line;
565 while ((line = sr.ReadLine()) != null)
566 {
567 if (line.Contains(",") || line.Contains("-"))
568 {
569 return true;
570 }
571 }
572 }
573 }
574 return false;
575 }
576
581 public static int AndroidGetNumBigCores()
582 {
583 int maxFreqKHzMin = int.MaxValue;
584 int maxFreqKHzMax = 0;
585 List<int> cpuMaxFreqKHz = new List<int>();
586 List<bool> cpuIsSmtCpu = new List<bool>();
587
588 try
589 {
590 string cpuPath = "/sys/devices/system/cpu/";
591 int coreIndex;
592 if (Directory.Exists(cpuPath))
593 {
594 foreach (string cpuDir in Directory.GetDirectories(cpuPath))
595 {
596 string dirName = Path.GetFileName(cpuDir);
597 if (!dirName.StartsWith("cpu")) continue;
598 if (!int.TryParse(dirName.Substring(3), out coreIndex)) continue;
599
600 int maxFreqKHz = GetMaxFreqKHz(coreIndex);
601 cpuMaxFreqKHz.Add(maxFreqKHz);
602 if (maxFreqKHz > maxFreqKHzMax) maxFreqKHzMax = maxFreqKHz;
603 if (maxFreqKHz < maxFreqKHzMin) maxFreqKHzMin = maxFreqKHz;
604 cpuIsSmtCpu.Add(IsSmtCpu(coreIndex));
605 }
606 }
607 }
608 catch (Exception e)
609 {
610 LogError(e.Message);
611 }
612
613 int numBigCores = 0;
614 int numCores = SystemInfo.processorCount;
615 int maxFreqKHzMedium = (maxFreqKHzMin + maxFreqKHzMax) / 2;
616 if (maxFreqKHzMedium == maxFreqKHzMax) numBigCores = numCores;
617 else
618 {
619 for (int i = 0; i < cpuMaxFreqKHz.Count; i++)
620 {
621 if (cpuIsSmtCpu[i] || cpuMaxFreqKHz[i] >= maxFreqKHzMedium) numBigCores++;
622 }
623 }
624
625 if (numBigCores == 0) numBigCores = SystemInfo.processorCount / 2;
626 else numBigCores = Math.Min(numBigCores, SystemInfo.processorCount);
627
628 return numBigCores;
629 }
630
636 {
637 List<int> capacities = new List<int>();
638 int minCapacity = int.MaxValue;
639 try
640 {
641 string cpuPath = "/sys/devices/system/cpu/";
642 int coreIndex;
643 if (Directory.Exists(cpuPath))
644 {
645 foreach (string cpuDir in Directory.GetDirectories(cpuPath))
646 {
647 string dirName = Path.GetFileName(cpuDir);
648 if (!dirName.StartsWith("cpu")) continue;
649 if (!int.TryParse(dirName.Substring(3), out coreIndex)) continue;
650
651 string capacityPath = Path.Combine(cpuDir, "cpu_capacity");
652 if (!File.Exists(capacityPath)) break;
653
654 int capacity = int.Parse(File.ReadAllText(capacityPath).Trim());
655 capacities.Add(capacity);
656 if (minCapacity > capacity) minCapacity = capacity;
657 }
658 }
659 }
660 catch (Exception e)
661 {
662 LogError(e.Message);
663 }
664
665 int numBigCores = 0;
666 foreach (int capacity in capacities)
667 {
668 if (capacity >= 2 * minCapacity) numBigCores++;
669 }
670
671 if (numBigCores == 0 || numBigCores > SystemInfo.processorCount) numBigCores = SystemInfo.processorCount;
672 return numBigCores;
673 }
674 }
675}
Class implementing helper functions for setup and process management.
static int AndroidGetNumBigCoresCapacity()
Calculates the number of big cores in Android similarly to Unity (https://docs.unity3d....
static void RemoveErrorCallBack(Callback< string > callback)
Remove callback function added for error logs.
static string LLMManagerPath
Path of file with build information for runtime.
static string LlamaLibReleaseURL
LlamaLib release url.
static string libraryPath
LlamaLib path.
static void ClearErrorCallBacks()
Remove all callback function added for error logs.
static string modelDownloadPath
Model download path.
static string LlamaLibVersion
LlamaLib version.
static int AndroidGetNumBigCores()
Calculates the number of big cores in Android similarly to ncnn (https://github.com/Tencent/ncnn)
static string Version
LLM for Unity version.
static string libraryName
LlamaLib name.
static readonly Dictionary< string,(string, string, string)[]> modelOptions
Default models for download.
static string LLMUnityStore
LLMnity store path.
static string LlamaLibExtensionURL
LlamaLib extension url.
static string LlamaLibURL
LlamaLib url.
static void AddErrorCallBack(Callback< string > callback)
Add callback function to call for error logs.