Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
title: Add an LLM to your Android app with Arm's AI Chat library

minutes_to_complete: 15

who_is_this_for: Android App Developers who wish to add LLM capabilities to an app.

learning_objectives:
- Easily add an on-device LLM to any Android app

prerequisites:
- Android Studio
- Android phone for testing (in Developer Mode, with USB cable to connect)

author: Ben Clark

### Tags
skilllevels: Introductory
subjects: ML
armips:
- Arm AI Chat library
tools_software_languages:
- Kotlin
- Neon
- SVE2
- SME2
- LLM
operatingsystems:
- Android



further_reading:
- resource:
title: AI Chat - Explore and evaluate LLMs on Android and ChromeOS
link: https://developer.arm.com/community/arm-community-blogs/b/announcements/posts/ai-chat-explore-and-evaluate-llms-on-android-and-chromeos
type: blog
- resource:
title: Arm AI Chat LLM test app
link: https://play.google.com/store/apps/details?id=com.arm.aichat
type: example app
- resource:
title: AI Chat library @ Maven Central
link: https://central.sonatype.com/artifact/com.arm/ai-chat
type: documentation
- resource:
title: AI Chat library on GitHub
link: https://github.com/arm/ai-chat
type: website
- resource:
title: Arm KleidiAI - Helping AI frameworks elevate their performance on Arm CPUs
link: https://developer.arm.com/community/arm-community-blogs/b/ai-blog/posts/kleidiai
type: blog
- resource:
title: SME2
link: https://www.arm.com/technologies/sme2
type: website


### FIXED, DO NOT MODIFY
# ================================================================================
weight: 1 # _index.md always has weight of 1 to order correctly
layout: "learningpathall" # All files under learning paths have this same wrapper
learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content.
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# ================================================================================
# FIXED, DO NOT MODIFY THIS FILE
# ================================================================================
weight: 21 # The weight controls the order of the pages. _index.md always has weight 1.
title: "Next Steps" # Always the same, html page title.
layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing.
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: Add AI Chat Library
weight: 3

### FIXED, DO NOT MODIFY
layout: learningpathall
---

Your freshly created Android Studio project should already have top-level repositories including `google()` and `mavenCentral()` in `settings.gradle.kts`, but check that it does, to ensure the app can find the library.

## Add the Maven Dependency
We need to add the AI Chat library in the app module build file, `app/build.gradle.kts` (not the project `build.gradle.kts` in the root of the project).

In here we add into the `dependencies` section at the bottom:
```kotlin
implementation("com.arm:ai-chat:0.1.0")
```

This adds the library to your project, and all the LLM functionality. Also in this file, check that:
- `targetSdk` and `compileSdk` are 36
- the Java version in `compileOptions` is `JavaVersion.VERSION_17`
- the `jvmTarget` in `kotlinOptions` is 17

In `libs.version.toml` we need to change the `kotlin` version to `2.2.20`, as the library requires a more recent version than the default.

Finally, there will be a banner at the top of these settings files saying the "Gradle files have changed since last project sync." Choose the option to "Sync Now", and let the project update.

## Adjust the Manifest
We need to adjust the `AndroidManifest.xml` file, which is in `app\src\main`.

Most of the file is an xml tag `<application ...>`. Add in among the other similar looking options in the tag:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Add in among the other similar looking options in the tag" reads a bit confusing? Maybe rephrase?

```xml
<application
android:extractNativeLibs="true"
... >
```
This enables the app to load the needed llama.cpp native libraries.
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
title: Add UI Code
weight: 4

### FIXED, DO NOT MODIFY
layout: learningpathall
---

## Add Layouts
Currently the `activity_main.xml` layout file in your `app\src\res\layout` directory is nearly empty, containing just a "Hello World!" piece of text that we wish to remove. Instead, replace it with the following, which will create a status area at the top, a place for messages in the middle, and a place for you to type at the bottom with a button to send:
```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/status_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="Loading model..."
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/messages"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="16dp"
android:clipToPadding="false"
android:paddingBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/input_row"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/status_text" />

<LinearLayout
android:id="@+id/input_row"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<EditText
android:id="@+id/user_input"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:enabled="false"
android:hint="Model is loading..."
android:inputType="textMultiLine"
android:maxLines="4"
android:minHeight="48dp"
android:padding="12dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/send_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:enabled="false"
android:text="Import model" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
```

We also need two small "helper" layouts to format the messages from the user and the AI assistant. In the `layout` folder alongside the main layout, first create `item_message_user.xml` and insert the following:
```xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:paddingBottom="6dp">

<TextView
android:id="@+id/msg_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:background="#D7F0FF"
android:padding="12dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" />
</FrameLayout>
```

Then create `item_message_assistant.xml` and put the following as its contents:
```xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:paddingBottom="6dp">

<TextView
android:id="@+id/msg_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:background="#EFEFEF"
android:padding="12dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" />
</FrameLayout>
```

## Add the MessageAdaptor class
As a final bit of UI code, we add a `MessageAdapter.kt` code file to put our messages into the correct bit of layout. The file should sit alongside the `MainActivity` class that is auto-created with the project.

The file contents of `MessageAdapter.kt` are:
```kotlin
package com.example.simpleaichat

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

data class Message(
val id: String,
val content: String,
val isUser: Boolean
)

class MessageAdapter(
private val messages: List<Message>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

override fun getItemViewType(position: Int): Int {
return if (messages[position].isUser) VIEW_TYPE_USER else VIEW_TYPE_ASSISTANT
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return if (viewType == VIEW_TYPE_USER) {
UserMessageViewHolder(inflater.inflate(R.layout.item_message_user, parent, false))
} else {
AssistantMessageViewHolder(inflater.inflate(R.layout.item_message_assistant, parent, false))
}
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.findViewById<TextView>(R.id.msg_content).text = messages[position].content
}

override fun getItemCount(): Int = messages.size

class UserMessageViewHolder(view: View) : RecyclerView.ViewHolder(view)
class AssistantMessageViewHolder(view: View) : RecyclerView.ViewHolder(view)

companion object {
private const val VIEW_TYPE_USER = 1
private const val VIEW_TYPE_ASSISTANT = 2
}
}
```

Quickly going through the important parts:
- `Message` is the smallest useful chat piece: one id, the message text, and a flag saying whether it came from the user or the assistant.
- `getItemViewType(...)` decides which row layout to use for each message.
- `onCreateViewHolder(...)` inflates either the user bubble or the assistant bubble layout.
- `onBindViewHolder(...)` writes the current message text into the row.
- `getItemCount()` tells the `RecyclerView` how many chat rows it should render.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: Add Model And Run
weight: 6

### FIXED, DO NOT MODIFY
layout: learningpathall
---

## Download a Mobile-compatible GGUF
Before you run you'll need to download a GGUF model file to run. To be mobile compatible, it'll need to run within your test Android phone's memory. A common Android memory size is 8GB, although some modern premium phones have more. Other things will also need to fit in memory, so the model size will need to be noticeably less than 8GB.

A good example model is [google_gemma-3-4b-it-Q4_0.gguf](https://huggingface.co/bartowski/google_gemma-3-4b-it-GGUF/blob/main/google_gemma-3-4b-it-Q4_0.gguf). Gemma 3 is a powerful model, and this 4 billion parameter version has been int4 quantized with the Q4_0 schema that works particularly well with Arm's [KleidiAI library](https://developer.arm.com/ai/kleidi-libraries), enabling speed-ups on phones with [SME2](https://www.arm.com/technologies/sme2), [SVE2](https://developer.arm.com/documentation/102340/0100/Introducing-SVE2) and [Neon](https://www.arm.com/technologies/neon).

Download Gemma 3 or another suitable model onto your phone, ready to run the app.

## Run the App!
In Android Studio, if you connect your test Android phone with a USB cable to your computer, you should now be able to run your LLM chatbot app. Make sure when you connect the phone it is in Developer Mode and you allow USB debugging.

In the bottom right there is a button "Import model". Clicking this will take you to downloads to be able to select the model you've downloaded, so the app can download it. Once it has finished copying and loading the model it will say "Model ready" at the top of the screen. Now if you click the text entry area at the bottom, you can type your questions and chat with the LLM.

![App screenshot#center](app_screenshot.jpg "Figure 1. AI Chat app screenshot interacting with Gemma 3 4B")
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: Project Setup
weight: 2

### FIXED, DO NOT MODIFY
layout: learningpathall
---

## Objective
In this learning path you will create a small chatbot Android app from scratch. The app will load a GGUF model of your choosing, and then run it in a chatbot format.

The app will use Arm's AI Chat library available from Maven Central, which provides an Android wrapper around llama.cpp, providing high-performance running of LLM models in the GGUF format.

For other examples of chatbots using this library you can use:
- the fully featured [Arm AI Chat app on Google Play](https://play.google.com/store/apps/details?id=com.arm.aichat), which can be used to test the performance and capabilities of mobile models, or
- the [AI Chat library GitHub example](https://github.com/arm/ai-chat/tree/use-maven-library/examples/llama.android), which is only slightly more complicated than this Learning Path.

## Project Setup
Open Android Studio and create a new project of the type "Empty Views Activity". Name it however you like, and leave other options on default - for instance Minimum SDK will be 33.

Loading
Loading