LiteRT multimodal lab#
Companion notebook for the video Run multimodal AI on Android with Python and LiteRT.
Connect to PythonHere#
%load_ext pythonhere
%connect-there
Show download progress#
%%there kv
<DownloadProgress>:
size_hint: .82, .82
pos_hint: {'center_x': .5, 'center_y': .5}
canvas:
Color:
rgba: .45, .52, .50, .16
Line:
width: dp(5)
circle:
self.center_x, \
self.center_y, \
min(self.width, self.height) * .34, \
0, \
360
Color:
rgba: .30, .40, .37, .95 if root.value > 0 else 0
Line:
width: dp(10) if root.value >= 100 else dp(9)
circle:
self.center_x, \
self.center_y, \
min(self.width, self.height) * .34, \
105, \
105 + 360 * min(max(root.value, 0), 100) / 100
Label:
text: "Ready" if root.value >= 100 else f"{root.value:3.0f}%"
font_size: min(root.width, root.height) * (.11 if root.value >= 100 else .13)
color: .25, .32, .30, .95
center: root.center
%%there
from kivy.properties import NumericProperty
from kivy.uix.widget import Widget
class DownloadProgress(Widget):
value = NumericProperty(0)
root.clear_widgets()
progress = DownloadProgress()
root.add_widget(progress)
Download the model#
%%there --worker
from pprint import pprint as pp
from kivy.clock import mainthread
from ml_here import download_hf_model, require_model
MODEL_REPO = "litert-community/SmolVLM2-500M"
MODEL_FILE = "SmolVLM2-500M.litertlm"
@mainthread
def update_progress(value):
progress.value = value.percent or 0.0
download_hf_model(
repo_id=MODEL_REPO,
filename=MODEL_FILE,
progress=update_progress,
)
pp(require_model(MODEL_REPO, MODEL_FILE))
'/storage/emulated/0/Android/data/me.herethere.pythonhere_dev/files/models/litert-community/SmolVLM2-500M/SmolVLM2-500M.litertlm'
Load the model#
%%there --worker
import litert_lm
engine = litert_lm.Engine(
require_model(MODEL_REPO, MODEL_FILE),
backend=litert_lm.Backend.GPU(),
vision_backend=litert_lm.Backend.GPU(),
enable_benchmark=True,
)
pp(engine)
Engine(model_path='/storage/emulated/0/Android/data/me.herethere.pythonhere_dev/files/models/litert-community/SmolVLM2-500M/SmolVLM2-500M.litertlm',
backend=GPU(gpu_decode_steps_per_sync=None),
max_num_tokens=None,
max_num_images=None,
cache_dir=None,
vision_backend=GPU(gpu_decode_steps_per_sync=None),
audio_backend=None,
enable_speculative_decoding=None,
lora_rank_config=None,
activation_data_type=None,
use_ringbuffers_local_attention=None,
enable_ynnpack=False)
Generate text#
Create a streaming output view#
%%there kv
<ModelOutput>:
size_hint: .88, .7
pos_hint: {'center_x': .5, 'center_y': .5}
Label:
markup: True
text: root.text
font_size: dp(21)
halign: "left"
valign: "top"
text_size: root.width, None
pos: root.pos
size: root.size
%%there
from kivy.properties import StringProperty
from kivy.uix.widget import Widget
from kivy.clock import mainthread
class ModelOutput(Widget):
text = StringProperty("")
committed = StringProperty("")
newest = StringProperty("")
@mainthread
def append_chunk(self, chunk):
self.committed += self.newest
self.newest = chunk
self.text = (
f"[color=#E8EEF3]{self.committed}[/color]"
f"[color=#FFD343][b]{self.newest}[/b][/color]"
)
root.clear_widgets()
output = ModelOutput()
root.add_widget(output)
Ask a text question#
%%there --worker
sampler = litert_lm.SamplerConfig(
temperature=0.7,
top_p=0.9,
top_k=40,
)
with engine.create_conversation(
sampler_config=sampler,
max_output_tokens=128,
) as conversation:
for chunk in conversation.send_message_async(
"What is on-device AI? Give a direct answer in one short paragraph."
):
output.append_chunk(chunk["content"][0]["text"])
info = conversation.get_benchmark_info()
output.append_chunk("")
Show benchmark results#
%%there
print(
"\n\tBenchmark info for the conversation\n"
f"Init: {info.init_time_in_second:.2f}s\n"
f"TTFT: {info.time_to_first_token_in_second:.2f}s\n"
f"Prefill: {info.last_prefill_token_count} tokens "
f"@ {info.last_prefill_tokens_per_second:.1f} tok/s\n"
f"Decode: {info.last_decode_token_count} tokens "
f"@ {info.last_decode_tokens_per_second:.1f} tok/s"
)
Benchmark info for the conversation
Init: 8.35s
TTFT: 1.24s
Prefill: 23 tokens @ 19.8 tok/s
Decode: 84 tokens @ 11.9 tok/s
Describe a camera photo#
Request camera permission#
%%there
from threading import Event
from android.permissions import Permission, request_permission
done = Event()
request_permission(Permission.CAMERA, lambda *_: done.set())
done.wait()
Preview and capture a photo#
%%there kv
AnchorLayout:
anchor_x: "center"
anchor_y: "center"
Camera:
id: camera
play: True
resolution: (640, 480)
fit_mode: "contain"
size_hint: None, None
height: min(root.width, root.height * 3 / 4)
width: self.height * 4 / 3
canvas.before:
PushMatrix
Rotate:
angle: -90
origin: self.center
canvas.after:
PopMatrix
%%there
camera = root.ids.camera
camera.play = False
%%there
from os.path import abspath, getsize
from PIL import Image
texture = camera.texture
image = Image.frombytes("RGBA", texture.size, texture.pixels)
# Kivy textures use a bottom-left origin.
image = image.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
# Match the rotation applied to the Android camera preview.
image = image.rotate(-90, expand=True).convert("RGB")
photo_path = abspath("from_camera.jpg")
image.save(photo_path, quality=90)
print(f"Photo: {photo_path}")
print(
f"Image: {image.width} × {image.height}, "
f"{image.mode}, {getsize(photo_path) / 1024:.1f} KiB"
)
Photo: /data/data/me.herethere.pythonhere_dev/files/upload/from_camera.jpg
Image: 480 × 640, RGB, 38.2 KiB
Generate a description#
%%there --worker
sampler = litert_lm.SamplerConfig(
temperature=1.5,
top_p=0.9,
top_k=40,
)
with engine.create_conversation(
sampler_config=sampler,
max_output_tokens=96,
) as conversation:
response = conversation.send_message(
litert_lm.Contents.of(
"What is it?",
litert_lm.Content.ImageFile(absolute_path=photo_path),
)
)
print(response["content"][0]["text"])
A mug likely belongs to the maker of Android or Kinder Stills for Samsung of Ireland with the Kitter Still Designs company. Here's a visual guide to understanding what could possibly fit inside the mug. However, this is purely speculative:
A mug likely fits a teacup. The teacup has a handle, a handle inside it is difficult to determine and may not fit directly with the interior inside. It might fit into what looks similar to a m
Explore sampling variability#
Generate responses with different temperatures and seeds#
%%there --worker
prompt = (
"Describe the main object in this image clearly and concisely "
"in 2-3 sentences."
)
temperatures = [0.2, 0.5, 0.7, 1.0]
seeds = [1, 7, 42, 123, 999]
experiment_results = []
for temperature in temperatures:
for seed in seeds:
sampler = litert_lm.SamplerConfig(
temperature=temperature,
top_p=0.9,
top_k=40,
seed=seed,
)
with engine.create_conversation(
sampler_config=sampler,
max_output_tokens=96,
) as conversation:
response = conversation.send_message(
litert_lm.Contents.of(
prompt,
litert_lm.Content.ImageFile(absolute_path=photo_path),
)
)
info = conversation.get_benchmark_info()
experiment_results.append({
"temperature": temperature,
"seed": seed,
"output": response["content"][0]["text"].strip(),
"ttft_s": info.time_to_first_token_in_second,
"prefill_tokens": info.last_prefill_token_count,
"prefill_tok_s": info.last_prefill_tokens_per_second,
"decode_tokens": info.last_decode_token_count,
"decode_tok_s": info.last_decode_tokens_per_second,
})
print(f"Done: {len(experiment_results)} runs")
Done: 20 runs
Compare the outputs#
import pandas as pd
experiment_results = %there get experiment_results
df = pd.DataFrame(experiment_results)
display(
df[
[
"temperature",
"seed",
"decode_tokens",
"output",
]
].style
.format({
"temperature": "{:.1f}",
})
.set_properties(
subset=["output"],
**{
"white-space": "pre-wrap",
"text-align": "left",
},
)
.hide(axis="index")
)
| temperature | seed | decode_tokens | output |
|---|---|---|---|
| 0.2 | 1 | 20 | The main object in the image is a mug with a cartoon penguin and Android mascot. |
| 0.2 | 7 | 20 | The main object in the image is a mug with a cartoon penguin and Android robot design. |
| 0.2 | 42 | 35 | The main object in the image is a mug with a cartoon character design. The mug is clear and has a handle, which is placed on a white surface. |
| 0.2 | 123 | 35 | The main object in the image is a mug with a cartoon character design. The mug has a handle on the right side and is placed on a white surface. |
| 0.2 | 999 | 21 | The main object in the image is a mug with a cartoon penguin and Android character on it. |
| 0.5 | 1 | 55 | The main object in the image is a mug with a cartoon design. The mug features an Android robot and a penguin on it. The robot is green and has a yellow car on its head. The penguin is blue and has a yellow car on its head. |
| 0.5 | 7 | 20 | The main object in the image is a mug with a cartoon penguin and Android robot design. |
| 0.5 | 42 | 67 | The main object in the image is a mug with a cartoon character. The mug is clear and has a handle, which is located on the right side of the mug. The cartoon character is a robot and a penguin. The mug is placed on a white surface, which is likely a counter or a table. |
| 0.5 | 123 | 19 | The main object in the image is a mug with a cartoon penguin and robot design. |
| 0.5 | 999 | 20 | The main object in the image is a mug with a cartoon penguin and Android mascot. |
| 0.7 | 1 | 39 | The main object in the image is a mug with a cartoon drawing of a penguin, a smiling face, and a small blue robot. The mug is placed on a white surface. |
| 0.7 | 7 | 46 | The main object in the image is a mug with a cartoon design. The mug has a handle on the right side and a small cartoon character on it. The character is green with a blue bird on its head. |
| 0.7 | 42 | 47 | The main object in the image is a mug with a cartoon character drawing on it. The mug has a handle and is decorated with a cartoon character, including a blue penguin. The cartoon character is riding a skateboard. |
| 0.7 | 123 | 19 | The main object in the image is a mug with a cartoon penguin and robot drawing. |
| 0.7 | 999 | 27 | The main object in the image is a mug with a cartoon design. The mug features a cartoon robot and penguin characters. |
| 1.0 | 1 | 53 | The main object in the image is a mug with a design of an Android character with a bird and a penguin. The mug has a handle, a lid, and is decorated with a design of an Android character, a bird, and a penguin. |
| 1.0 | 7 | 24 | The main object in the image is a clear mug that has a cartoon penguin and android drawing on it. |
| 1.0 | 42 | 18 | The main object in the image is a mug featuring a cartoon character on it. |
| 1.0 | 123 | 95 | The main object in the image is a mug with an illustration of an android and penguin. The mug is set against a white background, which is commonly used in presentations to keep the focus on the subject without distractions. The illustration is centered, with a small, stylized mug icon in the top right corner, which suggests that the mug is not an actual mug, but rather an example to illustrate or enhance the mug's design or functionality. |
| 1.0 | 999 | 37 | The main object in the image is a mug with a picture of a robot and a penguin. The mug is made of glass and is colored in a light blue color. |
Clean up#
%%there --worker
engine.close()