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