LLM for Unity  v2.4.2
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.2";
106 public static string LlamaLibVersion = "v1.2.3";
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 ("Qwen 2.5 7B", "https://huggingface.co/lmstudio-community/Qwen2.5-7B-Instruct-GGUF/resolve/main/Qwen2.5-7B-Instruct-Q4_K_M.gguf?download=true", null),
131 ("DeepSeek R1 Distill Llama 8B", "https://huggingface.co/lmstudio-community/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q4_K_M.gguf?download=true", null),
132 ("DeepSeek R1 Distill Qwen 7B", "https://huggingface.co/lmstudio-community/DeepSeek-R1-Distill-Qwen-7B-GGUF/resolve/main/DeepSeek-R1-Distill-Qwen-7B-Q4_K_M.gguf?download=true", null),
133 ("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"),
134 ("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),
135 ("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),
136 }},
137 {"Small models", new(string, string, string)[]
138 {
139 ("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),
140 ("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),
141 ("Qwen 2.5 3B", "https://huggingface.co/Qwen/Qwen2.5-3B-Instruct-GGUF/resolve/main/qwen2.5-3b-instruct-q4_k_m.gguf?download=true", null),
142 }},
143 {"Tiny models", new(string, string, string)[]
144 {
145 ("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),
146 ("Qwen 2.5 1.5B", "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/qwen2.5-1.5b-instruct-q4_k_m.gguf?download=true", null),
147 ("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),
148 ("DeepSeek R1 Distill Qwen 1.5B", "https://huggingface.co/lmstudio-community/DeepSeek-R1-Distill-Qwen-1.5B-GGUF/resolve/main/DeepSeek-R1-Distill-Qwen-1.5B-Q4_K_M.gguf?download=true", null),
149 }},
150 {"RAG models", new(string, string, string)[]
151 {
152 ("All MiniLM L12 v2", "https://huggingface.co/leliuga/all-MiniLM-L12-v2-GGUF/resolve/main/all-MiniLM-L12-v2.Q4_K_M.gguf", null),
153 ("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),
154 ("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),
155 ("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),
156 }},
157 };
158
160 [LLMUnity] public static DebugModeType DebugMode = DebugModeType.All;
161 static string DebugModeKey = "DebugMode";
162 public static bool FullLlamaLib = false;
163 static string FullLlamaLibKey = "FullLlamaLib";
164 static List<Callback<string>> errorCallbacks = new List<Callback<string>>();
165 static readonly object lockObject = new object();
166 static Dictionary<string, Task> androidExtractTasks = new Dictionary<string, Task>();
167
168 public enum DebugModeType
169 {
170 All,
171 Warning,
172 Error,
173 None
174 }
175
176 public static void Log(string message)
177 {
178 if ((int)DebugMode > (int)DebugModeType.All) return;
179 Debug.Log(message);
180 }
181
182 public static void LogWarning(string message)
183 {
184 if ((int)DebugMode > (int)DebugModeType.Warning) return;
185 Debug.LogWarning(message);
186 }
187
188 public static void LogError(string message)
189 {
190 if ((int)DebugMode > (int)DebugModeType.Error) return;
191 Debug.LogError(message);
192 foreach (Callback<string> errorCallback in errorCallbacks) errorCallback(message);
193 }
194
195 static void LoadPlayerPrefs()
196 {
197 DebugMode = (DebugModeType)PlayerPrefs.GetInt(DebugModeKey, (int)DebugModeType.All);
198 FullLlamaLib = PlayerPrefs.GetInt(FullLlamaLibKey, 0) == 1;
199 }
200
201 public static void SetDebugMode(DebugModeType newDebugMode)
202 {
203 if (DebugMode == newDebugMode) return;
204 DebugMode = newDebugMode;
205 PlayerPrefs.SetInt(DebugModeKey, (int)DebugMode);
206 PlayerPrefs.Save();
207 }
208
209#if UNITY_EDITOR
210 public static void SetFullLlamaLib(bool value)
211 {
212 if (FullLlamaLib == value) return;
213 FullLlamaLib = value;
214 PlayerPrefs.SetInt(FullLlamaLibKey, value ? 1 : 0);
215 PlayerPrefs.Save();
216 _ = DownloadLibrary();
217 }
218
219#endif
220
221 public static string GetLibraryName(string version)
222 {
223 return $"undreamai-{version}-llamacpp";
224 }
225
226 public static string GetAssetPath(string relPath = "")
227 {
228 string assetsDir = Application.platform == RuntimePlatform.Android ? Application.persistentDataPath : Application.streamingAssetsPath;
229 return Path.Combine(assetsDir, relPath).Replace('\\', '/');
230 }
231
232 public static string GetDownloadAssetPath(string relPath = "")
233 {
234 string assetsDir = (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer) ? Application.persistentDataPath : Application.streamingAssetsPath;
235 return Path.Combine(assetsDir, relPath).Replace('\\', '/');
236 }
237
238#if UNITY_EDITOR
239 [InitializeOnLoadMethod]
240 static async Task InitializeOnLoad()
241 {
242 LoadPlayerPrefs();
243 await DownloadLibrary();
244 }
245
246#else
247 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
248 void InitializeOnLoad()
249 {
250 LoadPlayerPrefs();
251 }
252
253#endif
254
255 static Dictionary<string, ResumingWebClient> downloadClients = new Dictionary<string, ResumingWebClient>();
256
257 public static void CancelDownload(string savePath)
258 {
259 if (!downloadClients.ContainsKey(savePath)) return;
260 downloadClients[savePath].CancelDownloadAsync();
261 downloadClients.Remove(savePath);
262 }
263
264 public static async Task DownloadFile(
265 string fileUrl, string savePath, bool overwrite = false,
266 Callback<string> callback = null, Callback<float> progressCallback = null
267 )
268 {
269 if (File.Exists(savePath) && !overwrite)
270 {
271 Log($"File already exists at: {savePath}");
272 }
273 else
274 {
275 Log($"Downloading {fileUrl} to {savePath}...");
276 string tmpPath = Path.Combine(Application.temporaryCachePath, Path.GetFileName(savePath));
277
278 ResumingWebClient client = new ResumingWebClient();
279 downloadClients[savePath] = client;
280 await client.DownloadFileTaskAsyncResume(new Uri(fileUrl), tmpPath, !overwrite, progressCallback);
281 downloadClients.Remove(savePath);
282#if UNITY_EDITOR
283 AssetDatabase.StartAssetEditing();
284#endif
285 Directory.CreateDirectory(Path.GetDirectoryName(savePath));
286 File.Move(tmpPath, savePath);
287#if UNITY_EDITOR
288 AssetDatabase.StopAssetEditing();
289#endif
290 Log($"Download complete!");
291 }
292
293 progressCallback?.Invoke(1f);
294 callback?.Invoke(savePath);
295 }
296
297 public static async Task AndroidExtractFile(string assetName, bool overwrite = false, bool log = true, int chunkSize = 1024*1024)
298 {
299 Task extractionTask;
300 lock (lockObject)
301 {
302 if (!androidExtractTasks.TryGetValue(assetName, out extractionTask))
303 {
304#if UNITY_ANDROID
305 extractionTask = AndroidExtractFileOnce(assetName, overwrite, log, chunkSize);
306#else
307 extractionTask = Task.CompletedTask;
308#endif
309 androidExtractTasks[assetName] = extractionTask;
310 }
311 }
312 await extractionTask;
313 }
314
315 public static async Task AndroidExtractFileOnce(string assetName, bool overwrite = false, bool log = true, int chunkSize = 1024*1024)
316 {
317 string source = "jar:file://" + Application.dataPath + "!/assets/" + assetName;
318 string target = GetAssetPath(assetName);
319 if (!overwrite && File.Exists(target))
320 {
321 if (log) Log($"File {target} already exists");
322 return;
323 }
324
325 Log($"Extracting {source} to {target}");
326
327 // UnityWebRequest to read the file from StreamingAssets
328 UnityWebRequest www = UnityWebRequest.Get(source);
329 // Send the request and await its completion
330 var operation = www.SendWebRequest();
331
332 while (!operation.isDone) await Task.Delay(1);
333 if (www.result != UnityWebRequest.Result.Success)
334 {
335 LogError("Failed to load file from StreamingAssets: " + www.error);
336 }
337 else
338 {
339 byte[] buffer = new byte[chunkSize];
340 using (Stream responseStream = new MemoryStream(www.downloadHandler.data))
341 using (FileStream fileStream = new FileStream(target, FileMode.Create, FileAccess.Write))
342 {
343 int bytesRead;
344 while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
345 {
346 await fileStream.WriteAsync(buffer, 0, bytesRead);
347 }
348 }
349 }
350 }
351
352 public static async Task AndroidExtractAsset(string path, bool overwrite = false)
353 {
354 if (Application.platform != RuntimePlatform.Android) return;
355 await AndroidExtractFile(Path.GetFileName(path), overwrite);
356 }
357
358 public static string GetFullPath(string path)
359 {
360 return Path.GetFullPath(path).Replace('\\', '/');
361 }
362
363 public static bool IsSubPath(string childPath, string parentPath)
364 {
365 return GetFullPath(childPath).StartsWith(GetFullPath(parentPath), StringComparison.OrdinalIgnoreCase);
366 }
367
368 public static string RelativePath(string fullPath, string basePath)
369 {
370 // Get the full paths and replace backslashes with forward slashes (or vice versa)
371 string fullParentPath = GetFullPath(basePath).TrimEnd('/');
372 string fullChildPath = GetFullPath(fullPath);
373
374 string relativePath = fullChildPath;
375 if (fullChildPath.StartsWith(fullParentPath, StringComparison.OrdinalIgnoreCase))
376 {
377 relativePath = fullChildPath.Substring(fullParentPath.Length);
378 while (relativePath.StartsWith("/")) relativePath = relativePath.Substring(1);
379 }
380 return relativePath;
381 }
382
383#if UNITY_EDITOR
384
385 [HideInInspector] public static float libraryProgress = 1;
386
387 public static void CreateEmptyFile(string path)
388 {
389 File.Create(path).Dispose();
390 }
391
392 static void ExtractInsideDirectory(string zipPath, string extractPath, bool overwrite = true)
393 {
394 using (ZipArchive archive = ZipFile.OpenRead(zipPath))
395 {
396 foreach (ZipArchiveEntry entry in archive.Entries)
397 {
398 if (string.IsNullOrEmpty(entry.Name)) continue;
399 string destinationPath = Path.Combine(extractPath, entry.FullName);
400 Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
401 entry.ExtractToFile(destinationPath, overwrite);
402 }
403 }
404 }
405
406 static async Task DownloadAndExtractInsideDirectory(string url, string path, string setupDir)
407 {
408 string urlName = Path.GetFileName(url);
409 string setupFile = Path.Combine(setupDir, urlName + ".complete");
410 if (File.Exists(setupFile)) return;
411
412 string zipPath = Path.Combine(Application.temporaryCachePath, urlName);
413 await DownloadFile(url, zipPath, true, null, SetLibraryProgress);
414
415 AssetDatabase.StartAssetEditing();
416 ExtractInsideDirectory(zipPath, path);
417 CreateEmptyFile(setupFile);
418 AssetDatabase.StopAssetEditing();
419
420 File.Delete(zipPath);
421 }
422
423 static void DeleteEarlierVersions()
424 {
425 List<string> assetPathSubDirs = new List<string>();
426 foreach (string dir in new string[] {GetAssetPath(), Path.Combine(Application.dataPath, "Plugins", "Android")})
427 {
428 if (Directory.Exists(dir)) assetPathSubDirs.AddRange(Directory.GetDirectories(dir));
429 }
430
431 Regex regex = new Regex(GetLibraryName("(.+)"));
432 foreach (string assetPathSubDir in assetPathSubDirs)
433 {
434 Match match = regex.Match(Path.GetFileName(assetPathSubDir));
435 if (match.Success)
436 {
437 string version = match.Groups[1].Value;
438 if (version != LlamaLibVersion)
439 {
440 Debug.Log($"Deleting other LLMUnity version folder: {assetPathSubDir}");
441 Directory.Delete(assetPathSubDir, true);
442 if (File.Exists(assetPathSubDir + ".meta")) File.Delete(assetPathSubDir + ".meta");
443 }
444 }
445 }
446 }
447
448 static async Task DownloadLibrary()
449 {
450 if (libraryProgress < 1) return;
451 libraryProgress = 0;
452
453 try
454 {
455 DeleteEarlierVersions();
456
457 string setupDir = Path.Combine(libraryPath, "setup");
458 Directory.CreateDirectory(setupDir);
459
460 // setup LlamaLib in StreamingAssets
461 await DownloadAndExtractInsideDirectory(LlamaLibURL, libraryPath, setupDir);
462
463 // setup LlamaLib extras in StreamingAssets
464 if (FullLlamaLib) await DownloadAndExtractInsideDirectory(LlamaLibExtensionURL, libraryPath, setupDir);
465 }
466 catch (Exception e)
467 {
468 LogError(e.Message);
469 }
470
471 libraryProgress = 1;
472 }
473
474 private static void SetLibraryProgress(float progress)
475 {
476 libraryProgress = Math.Min(0.99f, progress);
477 }
478
479 public static string AddAsset(string assetPath)
480 {
481 if (!File.Exists(assetPath))
482 {
483 LogError($"{assetPath} does not exist!");
484 return null;
485 }
486 string assetDir = GetAssetPath();
487 if (IsSubPath(assetPath, assetDir)) return RelativePath(assetPath, assetDir);
488
489 string filename = Path.GetFileName(assetPath);
490 string fullPath = GetAssetPath(filename);
491 AssetDatabase.StartAssetEditing();
492 foreach (string path in new string[] {fullPath, fullPath + ".meta"})
493 {
494 if (File.Exists(path)) File.Delete(path);
495 }
496 File.Copy(assetPath, fullPath);
497 AssetDatabase.StopAssetEditing();
498 return filename;
499 }
500
501#endif
503
505 public static void AddErrorCallBack(Callback<string> callback)
506 {
507 errorCallbacks.Add(callback);
508 }
509
511 public static void RemoveErrorCallBack(Callback<string> callback)
512 {
513 errorCallbacks.Remove(callback);
514 }
515
517 public static void ClearErrorCallBacks()
518 {
519 errorCallbacks.Clear();
520 }
521
522 public static int GetMaxFreqKHz(int cpuId)
523 {
524 string[] paths = new string[]
525 {
526 $"/sys/devices/system/cpu/cpufreq/stats/cpu{cpuId}/time_in_state",
527 $"/sys/devices/system/cpu/cpu{cpuId}/cpufreq/stats/time_in_state",
528 $"/sys/devices/system/cpu/cpu{cpuId}/cpufreq/cpuinfo_max_freq"
529 };
530
531 foreach (var path in paths)
532 {
533 if (!File.Exists(path)) continue;
534
535 int maxFreqKHz = 0;
536 using (StreamReader sr = new StreamReader(path))
537 {
538 string line;
539 while ((line = sr.ReadLine()) != null)
540 {
541 string[] parts = line.Split(' ');
542 if (parts.Length > 0 && int.TryParse(parts[0], out int freqKHz))
543 {
544 if (freqKHz > maxFreqKHz)
545 {
546 maxFreqKHz = freqKHz;
547 }
548 }
549 }
550 }
551 if (maxFreqKHz != 0) return maxFreqKHz;
552 }
553 return -1;
554 }
555
556 public static bool IsSmtCpu(int cpuId)
557 {
558 string[] paths = new string[]
559 {
560 $"/sys/devices/system/cpu/cpu{cpuId}/topology/core_cpus_list",
561 $"/sys/devices/system/cpu/cpu{cpuId}/topology/thread_siblings_list"
562 };
563
564 foreach (var path in paths)
565 {
566 if (!File.Exists(path)) continue;
567 using (StreamReader sr = new StreamReader(path))
568 {
569 string line;
570 while ((line = sr.ReadLine()) != null)
571 {
572 if (line.Contains(",") || line.Contains("-"))
573 {
574 return true;
575 }
576 }
577 }
578 }
579 return false;
580 }
581
586 public static int AndroidGetNumBigCores()
587 {
588 int maxFreqKHzMin = int.MaxValue;
589 int maxFreqKHzMax = 0;
590 List<int> cpuMaxFreqKHz = new List<int>();
591 List<bool> cpuIsSmtCpu = new List<bool>();
592
593 try
594 {
595 string cpuPath = "/sys/devices/system/cpu/";
596 int coreIndex;
597 if (Directory.Exists(cpuPath))
598 {
599 foreach (string cpuDir in Directory.GetDirectories(cpuPath))
600 {
601 string dirName = Path.GetFileName(cpuDir);
602 if (!dirName.StartsWith("cpu")) continue;
603 if (!int.TryParse(dirName.Substring(3), out coreIndex)) continue;
604
605 int maxFreqKHz = GetMaxFreqKHz(coreIndex);
606 cpuMaxFreqKHz.Add(maxFreqKHz);
607 if (maxFreqKHz > maxFreqKHzMax) maxFreqKHzMax = maxFreqKHz;
608 if (maxFreqKHz < maxFreqKHzMin) maxFreqKHzMin = maxFreqKHz;
609 cpuIsSmtCpu.Add(IsSmtCpu(coreIndex));
610 }
611 }
612 }
613 catch (Exception e)
614 {
615 LogError(e.Message);
616 }
617
618 int numBigCores = 0;
619 int numCores = SystemInfo.processorCount;
620 int maxFreqKHzMedium = (maxFreqKHzMin + maxFreqKHzMax) / 2;
621 if (maxFreqKHzMedium == maxFreqKHzMax) numBigCores = numCores;
622 else
623 {
624 for (int i = 0; i < cpuMaxFreqKHz.Count; i++)
625 {
626 if (cpuIsSmtCpu[i] || cpuMaxFreqKHz[i] >= maxFreqKHzMedium) numBigCores++;
627 }
628 }
629
630 if (numBigCores == 0) numBigCores = SystemInfo.processorCount / 2;
631 else numBigCores = Math.Min(numBigCores, SystemInfo.processorCount);
632
633 return numBigCores;
634 }
635
641 {
642 List<int> capacities = new List<int>();
643 int minCapacity = int.MaxValue;
644 try
645 {
646 string cpuPath = "/sys/devices/system/cpu/";
647 int coreIndex;
648 if (Directory.Exists(cpuPath))
649 {
650 foreach (string cpuDir in Directory.GetDirectories(cpuPath))
651 {
652 string dirName = Path.GetFileName(cpuDir);
653 if (!dirName.StartsWith("cpu")) continue;
654 if (!int.TryParse(dirName.Substring(3), out coreIndex)) continue;
655
656 string capacityPath = Path.Combine(cpuDir, "cpu_capacity");
657 if (!File.Exists(capacityPath)) break;
658
659 int capacity = int.Parse(File.ReadAllText(capacityPath).Trim());
660 capacities.Add(capacity);
661 if (minCapacity > capacity) minCapacity = capacity;
662 }
663 }
664 }
665 catch (Exception e)
666 {
667 LogError(e.Message);
668 }
669
670 int numBigCores = 0;
671 foreach (int capacity in capacities)
672 {
673 if (capacity >= 2 * minCapacity) numBigCores++;
674 }
675
676 if (numBigCores == 0 || numBigCores > SystemInfo.processorCount) numBigCores = SystemInfo.processorCount;
677 return numBigCores;
678 }
679 }
680}
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.