You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: add Kotlin examples and fix MULTIMODAL alias note for Android LLM runner (#20147)
### Summary
Follow up to #8790,
#19611 (comment)
1. Added Kotlin snippets next to every Java block in
`docs/source/llm/run-on-android.md`. Same shape as `run-on-ios.md`, with
a `Language:` label paragraph above each fenced block (no tab-set).
Covers imports, the simple `LlmModule` constructor, the
`LlmModuleConfig` builder, `load`, the `LlmCallback` object expression,
`LlmGenerationConfig`, `stop`, `resetContext`, image prefill (`IntArray`
/ `ByteBuffer` / `FloatArray`), `prefillNormalizedImage`,
`prefillAudio`, `prefillRawAudio`, and the multimodal `generate`
overload. Uses idiomatic Kotlin (`object : LlmCallback`,
`IntArray`/`FloatArray`, `Float.SIZE_BYTES`, `ByteBuffer.apply { ... }`,
trailing commas).
2. Reworded the available-model-types sentence so it no longer lists
`MODEL_TYPE_MULTIMODAL` as a separate type. Both constants share the
value `2` in `LlmModuleConfig.java:53,56`, so listing them implied
behavior that does not exist. The new sentence notes the alias
explicitly.
cc @mergennachin@nil-is-all@kirklandsign@cbilgin@larryliu0820@cccclai@helunwencser@jackzhxng@digantdesai@byjlw
---------
Co-authored-by: Siddartha Pothapragada <sidart@meta.com>
Copy file name to clipboardExpand all lines: docs/source/llm/run-on-android.md
+170-8Lines changed: 170 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Running LLMs on Android
2
2
3
-
ExecuTorch's LLM-specific runtime components provide an experimental Java interface around the core C++ LLM runtime, available through the `executorch-android` AAR.
3
+
ExecuTorch's LLM-specific runtime components provide experimental Java APIs, callable from Java or Kotlin, around the core C++ LLM runtime. These APIs are available through the `executorch-android` AAR.
4
4
5
5
## Prerequisites
6
6
@@ -10,36 +10,64 @@ To add the `executorch-android` library to your app, see [Using ExecuTorch on An
10
10
11
11
## Runtime API
12
12
13
-
Once the `executorch-android` AAR is on your classpath, you can import the LLM runner classes from the `org.pytorch.executorch.extension.llm` package.
13
+
Once the `executorch-android` AAR is on your classpath, you can import the LLM runner classes from the `org.pytorch.executorch.extension.llm` package. The runner is callable from both Java and Kotlin; the rest of this guide includes both Java and Kotlin examples for each snippet.
// Only needed for the multimodal ByteBuffer paths in the Images section.
37
+
importjava.nio.ByteBuffer
38
+
importjava.nio.ByteOrder
22
39
```
23
40
24
41
### LlmModule
25
42
26
-
The `LlmModule` class provides a simple Java interface for loading a text-generation model, configuring its tokenizer, generating token streams, and stopping execution. It also supports multimodal models that accept image and audio inputs alongside a text prompt.
43
+
The `LlmModule` class provides a simple interface, usable from Java and Kotlin, for loading a text-generation model, configuring its tokenizer, generating token streams, and stopping execution. It also supports multimodal models that accept image and audio inputs alongside a text prompt.
27
44
28
45
This API is experimental and subject to change.
29
46
30
47
#### Initialization
31
48
32
49
Create an `LlmModule` by specifying paths to your serialized model (`.pte`) and tokenizer files. For text-only models, the simple constructor is enough:
33
50
51
+
Java:
34
52
```java
35
53
LlmModule module =newLlmModule(
36
54
"/data/local/tmp/llama-3.2-instruct.pte",
37
55
"/data/local/tmp/tokenizer.model",
38
56
0.8f);
39
57
```
40
58
59
+
Kotlin:
60
+
```kotlin
61
+
val module =LlmModule(
62
+
"/data/local/tmp/llama-3.2-instruct.pte",
63
+
"/data/local/tmp/tokenizer.model",
64
+
0.8f
65
+
)
66
+
```
67
+
41
68
For finer control (multimodal model type, BOS/EOS handling, supplementary data files, load mode), use `LlmModuleConfig` with the fluent builder:
Available load modes are `LOAD_MODE_FILE`, `LOAD_MODE_MMAP` (default), `LOAD_MODE_MMAP_USE_MLOCK`, and `LOAD_MODE_MMAP_USE_MLOCK_IGNORE_ERRORS`. Available model types are `MODEL_TYPE_TEXT`, `MODEL_TYPE_TEXT_VISION`, and `MODEL_TYPE_MULTIMODAL`.
Available load modes are `LOAD_MODE_FILE`, `LOAD_MODE_MMAP` (default), `LOAD_MODE_MMAP_USE_MLOCK`, and `LOAD_MODE_MMAP_USE_MLOCK_IGNORE_ERRORS`. Available model types are `MODEL_TYPE_TEXT` and `MODEL_TYPE_TEXT_VISION` (the `MODEL_TYPE_MULTIMODAL` constant is currently an alias for `MODEL_TYPE_TEXT_VISION` and selects the same runtime path).
56
97
57
98
Construction itself is lightweight and does not load the program data immediately.
58
99
59
100
#### Loading
60
101
61
102
Explicitly load the model before generation to avoid paying the load cost during your first `generate` call.
62
103
104
+
Java:
63
105
```java
64
106
int status = module.load();
65
107
if (status !=0) {
66
108
// Handle load failure (status is an ExecuTorch runtime error code).
67
109
}
68
110
```
69
111
112
+
Kotlin:
113
+
```kotlin
114
+
val status = module.load()
115
+
if (status !=0) {
116
+
// Handle load failure (status is an ExecuTorch runtime error code).
117
+
}
118
+
```
119
+
70
120
If you skip this step, the model is loaded lazily on the first `generate` call.
71
121
72
122
#### Generating
73
123
74
124
Generate tokens from a text prompt by passing an `LlmCallback` that receives each token as it is produced. The same callback also receives a JSON-encoded statistics string when generation completes.
75
125
126
+
Java:
76
127
```java
77
128
LlmCallback callback =newLlmCallback() {
78
129
@Override
@@ -97,8 +148,31 @@ LlmCallback callback = new LlmCallback() {
97
148
module.generate("Once upon a time", callback);
98
149
```
99
150
151
+
Kotlin:
152
+
```kotlin
153
+
val callback =object:LlmCallback {
154
+
overridefunonResult(token:String) {
155
+
// Called once per generated token. Append to your UI buffer here.
156
+
print(token)
157
+
}
158
+
159
+
overridefunonStats(statsJson:String) {
160
+
// Called once when generation finishes. See extension/llm/runner/stats.h
module.generate("Once upon a time", genConfig, callback);
110
184
```
111
185
186
+
Kotlin:
187
+
```kotlin
188
+
val genConfig =LlmGenerationConfig.create()
189
+
.seqLen(2048)
190
+
.temperature(0.8f)
191
+
.echo(false)
192
+
.build()
193
+
194
+
module.generate("Once upon a time", genConfig, callback)
195
+
```
196
+
112
197
`LlmGenerationConfig` exposes `echo`, `maxNewTokens`, `seqLen`, `temperature`, `numBos`, `numEos`, and `warming`. Defaults match the C++ `GenerationConfig` documented in [Running LLMs with C++](run-with-c-plus-plus.md).
113
198
114
199
#### Stopping Generation
115
200
116
201
If you need to interrupt a long-running generation, call `stop()` from another thread (or from inside the `onResult` callback):
117
202
203
+
Java:
118
204
```java
119
205
module.stop();
120
206
```
121
207
208
+
Kotlin:
209
+
```kotlin
210
+
module.stop()
211
+
```
212
+
122
213
Generation also runs synchronously on the calling thread, so make sure you invoke `generate()` off the main thread (for example, on a `HandlerThread` or via a `java.util.concurrent.Executor`).
123
214
124
215
#### Resetting
125
216
126
217
To clear the prefilled tokens from the KV cache and reset the start position to 0, call:
127
218
219
+
Java:
128
220
```java
129
221
module.resetContext();
130
222
```
131
223
224
+
Kotlin:
225
+
```kotlin
226
+
module.resetContext()
227
+
```
228
+
132
229
This is the equivalent of `reset()` on the iOS runner and `reset()` on the C++ `IRunner`.
133
230
134
231
### Multimodal Inputs
135
232
136
-
For models declared as `MODEL_TYPE_TEXT_VISION`or `MODEL_TYPE_MULTIMODAL`, image and audio data are provided through dedicated prefill methods. After prefilling all modalities, call `generate()` with the text prompt to produce the response.
233
+
For models declared as `MODEL_TYPE_TEXT_VISION`(`MODEL_TYPE_MULTIMODAL` is currently an alias), image and audio data are provided through dedicated prefill methods. After prefilling all modalities, call `generate()` with the text prompt to produce the response.
137
234
138
235
#### Images
139
236
140
237
Raw uint8 pixel data in CHW order can be supplied as an `int[]`, or as a direct `ByteBuffer` to avoid JNI array copies:
// As direct ByteBuffer (preferred for large images)
261
+
val rawBytes:ByteArray=... // length == channels * height * width
262
+
val buffer =ByteBuffer.allocateDirect(3*336*336).apply {
263
+
put(rawBytes)
264
+
rewind()
265
+
}
266
+
module.prefillImages(buffer, 336, 336, 3)
267
+
```
268
+
269
+
Pre-normalized float pixel data is also supported, both as a `float[]` and as a direct `ByteBuffer` in native byte order. The two paths intentionally hit different methods: the `float[]` overload is `prefillImages`, while the `ByteBuffer` path is `prefillNormalizedImage` (the names reflect the underlying JNI bindings and are not interchangeable).
See the [Llama Android demo app](https://github.com/meta-pytorch/executorch-examples/tree/main/llm/android/LlamaDemo) in `executorch-examples` for an end-to-end project that wires `LlmModule`, `LlmCallback`, and a `HandlerThread` into a chat UI.
0 commit comments