On-device AI with LiteRT#

PythonHere bundles the LiteRT-LM Python library in its Android app, so .litertlm models can run directly on the device. After a model has been downloaded, inference does not require a network connection.

PythonHere also provides model helpers for managing model storage and downloading model files from Hugging Face. This example covers every public helper, then loads a small language model and generates text with LiteRT-LM. Refer to the LiteRT-LM documentation for its full inference API, including sampling, streaming, and benchmarking.

For an interactive vision-language example using the Android camera, see the LiteRT multimodal lab, the companion notebook for the video Run multimodal AI on Android with Python and LiteRT.

Connect to PythonHere#

Load the Jupyter extension and connect to a running PythonHere app:

%load_ext pythonhere
%connect-there

Inspect model storage#

models_directory() returns PythonHere’s platform-specific model directory. model_path() builds the managed path for a particular Hugging Face model, and discover_models() lists models already present on the device.

%%there
from pprint import pprint as pp
from ml_here import (
    discover_models,
    download_hf_model,
    model_path,
    models_directory,
    require_model,
)

MODEL_REPO = "litert-community/SmolLM2-135M-Instruct"
MODEL_FILE = "SmolLM2_135M_Instruct.litertlm"

print(f"Model directory: {models_directory(create=True)}")
print(f"Managed path: {model_path(MODEL_REPO, MODEL_FILE)}")
print("Available LiteRT-LM models:")
pp(discover_models())
Model directory: /storage/emulated/0/Android/data/me.herethere.pythonhere_dev/files/models
Managed path: /storage/emulated/0/Android/data/me.herethere.pythonhere_dev/files/models/litert-community/SmolLM2-135M-Instruct/SmolLM2_135M_Instruct.litertlm
Available LiteRT-LM models:
['/storage/emulated/0/Android/data/me.herethere.pythonhere_dev/files/models/litert-community/SmolLM2-135M-Instruct/SmolLM2_135M_Instruct.litertlm']

Download the model#

download_hf_model() downloads the model into its managed path. Downloads can be resumed, and an existing verified file is reused. Because downloading may take time, this cell runs on a worker so the Android UI stays responsive.

%%there --worker
def report_progress(value):
    if value.percent is not None:
        print(f"\rDownloading: {value.percent:.1f}%", end="", flush=True)


downloaded_path = download_hf_model(
    repo_id=MODEL_REPO,
    filename=MODEL_FILE,
    progress=report_progress,
)

print(f"\nDownloaded model: {downloaded_path}")
Downloaded model: /storage/emulated/0/Android/data/me.herethere.pythonhere_dev/files/models/litert-community/SmolLM2-135M-Instruct/SmolLM2_135M_Instruct.litertlm

require_model() returns the model path if the file is available and raises FileNotFoundError otherwise. Discover the device again to confirm that the downloaded model is now included:

%%there
model_file = require_model(MODEL_REPO, MODEL_FILE)
print(f"Required model: {model_file}")
print("Available LiteRT-LM models:")
pp(discover_models())
Required model: /storage/emulated/0/Android/data/me.herethere.pythonhere_dev/files/models/litert-community/SmolLM2-135M-Instruct/SmolLM2_135M_Instruct.litertlm
Available LiteRT-LM models:
['/storage/emulated/0/Android/data/me.herethere.pythonhere_dev/files/models/litert-community/SmolLM2-135M-Instruct/SmolLM2_135M_Instruct.litertlm']

Load the model#

Model loading and inference can block the app’s UI thread, so they also run with %%there --worker.

%%there --worker
import litert_lm

engine = litert_lm.Engine(model_file)
pp(engine)
Engine(model_path='/storage/emulated/0/Android/data/me.herethere.pythonhere_dev/files/models/litert-community/SmolLM2-135M-Instruct/SmolLM2_135M_Instruct.litertlm',
       backend=CPU(thread_count=None),
       max_num_tokens=None,
       max_num_images=None,
       cache_dir=None,
       vision_backend=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 conversation and send a prompt. The model runs locally in the PythonHere app, and only the generated response is returned to Jupyter.

%%there --worker
with engine.create_conversation(
    max_output_tokens=128,
) as conversation:
    response = conversation.send_message(
        "What is the Kivy framework? Answer in one short paragraph."
    )
    pp(response["content"][0]["text"])
('The Kivy framework is a popular programming language and framework for '
 'building interactive and dynamic web applications. It is a cross-platform '
 'framework that allows developers to create web applications with a wide '
 'range of features, including web servers, databases, and APIs. Kivy is a '
 'cross-platform framework that allows developers to build web applications '
 'with a wide range of features, including web servers, databases, and APIs. '
 'It is a popular choice among developers for building web applications, as it '
 'provides a wide range of features and is easy to use.')

Clean up#

Close the engine to release its native resources when it is no longer needed:

%%there --worker
engine.close()