Quick Start
The example code in this quick start guide is provided for educational and demonstration purposes only. It may not represent best practices for production use.
Aim
UAI.LiteRTLM aims to provide a 1:1 interface to the LiteRT-LM cross-platform C API. Since LiteRT-LM's C API is still under development, this section explains the breaking changes policy and LiteRT-LM version support for UAI.LiteRTLM.
Versioning Scheme
This package tries to follow semantic versioning, with a few caveats:
All versions leading up to the stable release of the LiteRT-LM C API will be preview versions.
- Example:
2.1.0-preview.3,2.0.0-preview.1
- Example:
MINORversion updates (x.Y.z, whereYis theMINORversion number) indicate a change in the LiteRT-LM tag and/or commit ID used by the package.- Example: UAI.LiteRTLM
2.6.0-preview.x-> LiteRT-LMv0.17.1,2.5.0-preview.x-> LiteRT-LMv0.17.0
- Example: UAI.LiteRTLM
MINORversion updates may contain breaking changes due to changes in the LiteRT-LM C API.PATCHversion updates are represented by thepreviewlabel (x.y.0-preview.Z, whereZis thePATCHversion number) instead of the standardx.y.Zformat.PATCHversion updates aim to be backwards compatible, with exceptions for application-level metadata changes that may be required by the update.- Example:
2.1.0-preview.2switches from WebGPU to OpenCL acceleration on Android, requiring additional entries in the application'sAndroidManifest.xml.
- Example:
Prebuilt Binaries
While UAI.LiteRTLM aims to support all platforms supported by LiteRT-LM, it currently only includes prebuilt native binaries for a
subset of them, depending on the package version. The package aims to provide LiteRT-LM binaries without C patches and builds the default
c:litert-lm Bazel target with platform-specific command-line arguments. The build scripts for all platforms are available in
Native/. Please note that the scripts may modify or remove files
in the LiteRT-LM submodule's working tree. You can build your own binaries for additional platforms and include them in your
project's Plugins directory. UAI.LiteRTLM should use them as-is for that platform.
UAI.LiteRTLM also does not include every LiteRT-LM GPU accelerator. Only one accelerator is included per platform.
Do not include your own binaries for platforms or accelerators that UAI.LiteRTLM already provides binaries for, as this can cause conflicts. If you want to use your own binaries instead, customize the package and remove the included ones.
Build script contributions are always welcome!
API
The package API is roughly split into two parts: the low-level API and the high-level API.
Low-level API (NativeAPI)
The low-level API, exposed by the NativeAPI static class, is a simple wrapper around all functions exposed by the LiteRT-LM C API.
It works directly with pointers and MonoPInvokeCallbacks, so using this API requires you to manage native C memory yourself.
High-level API (ManagedAPI)
The high-level API, defined in ManagedAPI.cs, is a collection of handle classes that wrap all native structures defined by the
native API and implement IDisposable. All pointers and native callbacks are handled automatically, and some wrappers also
provide additional abstractions to make them easier to use. These wrapper classes also implicitly convert to IntPtr, allowing you to use
them with the native API if needed.
Currently, only one feature implemented by the native API is not exposed by the managed API. You cannot pass custom additional
data to streamed operations because the wrappers use their own data to track managed StreamCallbacks.
Compatability Table
| UAI.LiteRTLM | LiteRT-LM | Included Platforms | Included Accelerators |
|---|---|---|---|
| 2.7.0-preview.1+ | v0.18.0 (b2f686e) |
Android (arm64) macOS (arm64) iOS (arm64, sim_arm64) Windows (x64) |
CPU OpenCL (Android) Metal (macOS, iOS) WebGPU (Windows) |
| 2.6.0-preview.1+ | dadb79c (v0.17.1) |
Android (arm64) macOS (arm64) iOS (arm64, sim_arm64) Windows (x64) |
CPU OpenCL (Android) Metal (macOS, iOS) WebGPU (Windows) |
| 2.5.0-preview.1+ | v0.17.0 (e9fd8c5) |
Android (arm64) macOS (arm64) iOS (arm64, sim_arm64) Windows (x64) |
CPU OpenCL (Android) Metal (macOS, iOS) WebGPU (Windows) |
| 2.4.0-preview.1+ | v0.16.1 / v0.16.0 (924e79c) |
Android (arm64) macOS (arm64) iOS (arm64, sim_arm64) Windows (x64) |
CPU OpenCL (Android) Metal (macOS, iOS) WebGPU (Windows) |
| 2.3.0-preview.1+ | v0.16.0 (740f122) |
Android (arm64) macOS (arm64) iOS (arm64, sim_arm64) Windows (x64) |
CPU OpenCL (Android) Metal (macOS, iOS) WebGPU (Windows) |
| 2.2.0-preview.1+ | v0.15.0 (2117fc4) |
Android (arm64) macOS (arm64) iOS (arm64, sim_arm64) Windows (x64) |
CPU OpenCL (Android) Metal (macOS, iOS) WebGPU (Windows) |
| 2.1.0-preview.5+ | v0.15.0-alpha0 (ad53ed1) |
Android (arm64) macOS (arm64) iOS (arm64, sim_arm64) Windows (x64) |
CPU OpenCL (Android) Metal (macOS, iOS) WebGPU (Windows) |
LiteRT-LM Notes
Uses a fork of LiteRT-LM at v0.17.1 (5e58e9a) with commit a1b8ec1 cherry-picked onto it to fix #3247.
The cherry-pick was pushed to the latest-stable branch of my fork of LiteRT-LM, with commit ID dadb79c.
Android GPU Acceleration
To enable GPU acceleration on Android, add the following to the <application> section of your AndroidManifest.xml to enable OpenCL:
<uses-native-library android:name="libvndksupport.so" android:required="false"/>
<uses-native-library android:name="libOpenCL.so" android:required="false"/>
NuGet Package / .NET 6+
Starting with 2.2.0-preview.2, UAI.LiteRTLM is also available on NuGet for .NET 6+! It supports all the same platforms as the Unity package, and works with MAUI.
Because the main package includes some Unity-specific workarounds, using the NuGet package requires a small amount of additional setup.
Before creating the Engine, call Accelerators.LoadNativeLibraries() to pre-load the native accelerator libraries.
You only need to call this method once; subsequent calls are no-ops.
Example Script
Documentation for this package is still a WIP. The following example demonstrates a streamed single-turn conversation using the high-level API, including benchmark collection.
using Uralstech.UAI.LiteRTLM;
private async Awaitable RunConversation()
{
LiteRTLMNativeLogging.SetMinLogLevel(LogSeverity.Info);
string modelPath = Path.Join(Application.persistentDataPath, "model.litertlm");
string cacheDir = Path.Join(Application.temporaryCachePath, "modelCache");
Directory.CreateDirectory(cacheDir);
// Speculative decoding performs badly on WebGPU on Windows.
bool isWindows = Application.platform is RuntimePlatform.WindowsEditor
or RuntimePlatform.WindowsPlayer
or RuntimePlatform.WindowsServer;
bool supportsSpeculativeDecoding;
using (ModelInfo modelCapabilities = new(modelPath))
supportsSpeculativeDecoding = modelCapabilities.HasSpeculativeDecodingSupport();
using EngineSettings engineSettings = new(modelPath, BackendNames.GPU);
engineSettings.SetEnableSpeculativeDecoding(!isWindows && supportsSpeculativeDecoding);
engineSettings.SetCacheDir(cacheDir);
engineSettings.EnableBenchmark();
using Engine engine = new(engineSettings);
using ThinkingConfig thinkingConfig = new();
thinkingConfig.SetEnableThinking(false);
using ConversationConfig conversationConfig = new();
conversationConfig.SetThinkingConfig(thinkingConfig);
using Conversation conversation = new(engine, conversationConfig);
Debug.Log("Engine and conversation created.");
const string message = "{\"role\":\"user\",\"content\":\"What is the tallest building in the world?\"}";
TaskCompletionSource<bool> completionSource = new();
void OnChunk(StreamChunk chunk)
{
Debug.Log("Got chunk:"
+ $"\n\tText: {chunk.GetText()}"
+ $"\n\tIsFinal: {chunk.IsFinal()}"
+ $"\n\tError: {chunk.GetError()}"
);
if (chunk.IsFinal())
completionSource.TrySetResult(true);
}
int result = conversation.SendMessageStream(OnChunk, message);
Debug.Log("Message sent: " + (result == 0));
if (result == 0)
await completionSource.Task;
using BenchmarkInfo? benchmarkInfo = conversation.GetBenchmarkInfo();
if (benchmarkInfo == null)
{
Debug.Log("No benchmark info found.");
return;
}
IReadOnlyList<BenchmarkInfo.Turn> prefillTurns = benchmarkInfo.GetPrefillTurns();
IReadOnlyList<BenchmarkInfo.Turn> decodeTurns = benchmarkInfo.GetDecodeTurns();
Debug.Log("Benchmark info:"
+ $"\n\tInitialization time: {benchmarkInfo.GetTotalInitTime()}"
+ $"\n\tTime to first token: {benchmarkInfo.GetTimeToFirstToken()}"
+ $"\n\tPrefill tokens per second: {prefillTurns[0].TokensPerSecond}, for total: {prefillTurns[0].TokenCount} tokens"
+ $"\n\tDecode tokens per second: {decodeTurns[0].TokensPerSecond}, for total: {decodeTurns[0].TokenCount} tokens"
);
}