Skip to content

Commit a567abf

Browse files
cmodi-metafacebook-github-bot
authored andcommitted
Porting over ET MultiModal Demo App (#4455)
Summary: Pull Request resolved: #4455 Adding ET demo app for multimodal support. This is the first diff that supports Llama3. This includes major changes to the existing Llama Demo app with having the following features: 1. Llama3 support 2. Settings activity 3. UI/UX improvements to the mainactivity 4. Ability to add multi-images in prep for multimodal support. 5. Metrics Note: You'll need to build the `executorch-llama.aar` and have it placed in the `app/libs` folder. Reviewed By: kirklandsign Differential Revision: D60416605 fbshipit-source-id: 262329c30e1ec28c3905da5c040dc661307f8666
1 parent 9aeceee commit a567abf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2328
-169
lines changed

examples/demo-apps/android/LlamaDemo/app/build.gradle.kts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ android {
1717

1818
defaultConfig {
1919
applicationId = "com.example.executorchllamademo"
20-
minSdk = 24
20+
minSdk = 28
2121
targetSdk = 33
2222
versionCode = 1
2323
versionName = "1.0"
@@ -56,7 +56,10 @@ dependencies {
5656
implementation("androidx.camera:camera-core:1.3.0-rc02")
5757
implementation("androidx.constraintlayout:constraintlayout:2.2.0-alpha12")
5858
implementation("com.facebook.fbjni:fbjni:0.5.1")
59+
implementation("com.google.code.gson:gson:2.8.6")
5960
implementation(files("libs/executorch-llama.aar"))
61+
implementation("com.google.android.material:material:1.12.0")
62+
implementation("androidx.activity:activity:1.9.0")
6063
testImplementation("junit:junit:4.13.2")
6164
androidTestImplementation("androidx.test.ext:junit:1.1.5")
6265
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

examples/demo-apps/android/LlamaDemo/app/src/main/AndroidManifest.xml

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,44 @@
33
xmlns:tools="http://schemas.android.com/tools"
44
package="com.example.executorchllamademo">
55

6-
<uses-sdk android:minSdkVersion="19"
7-
android:targetSdkVersion="34"
8-
android:maxSdkVersion="40" />
6+
<uses-sdk
7+
android:maxSdkVersion="40"
8+
android:minSdkVersion="28"
9+
android:targetSdkVersion="34" />
910

1011
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
12+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
13+
<uses-permission android:name="android.permission.CAMERA" />
14+
15+
<uses-feature android:name="android.hardware.camera" />
1116

1217
<application
18+
android:name=".ETLogging"
1319
android:allowBackup="true"
1420
android:dataExtractionRules="@xml/data_extraction_rules"
21+
android:extractNativeLibs="true"
1522
android:fullBackupContent="@xml/backup_rules"
16-
android:icon="@mipmap/ic_launcher"
23+
android:icon="@drawable/logo"
1724
android:label="@string/app_name"
18-
android:roundIcon="@mipmap/ic_launcher_round"
1925
android:supportsRtl="true"
20-
android:theme="@style/Theme.ExecuTorchLlamaDemo"
21-
android:extractNativeLibs="true"
26+
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
2227
tools:targetApi="34">
28+
<activity
29+
android:name=".LogsActivity"
30+
android:exported="false" />
31+
<activity
32+
android:name=".SettingsActivity"
33+
android:exported="false" />
2334

24-
<uses-native-library android:name="libcdsprpc.so"
25-
android:required="false"/>
35+
<uses-native-library
36+
android:name="libcdsprpc.so"
37+
android:required="false" />
2638

2739
<activity
2840
android:name=".MainActivity"
2941
android:exported="true"
3042
android:label="@string/app_name"
31-
android:theme="@style/Theme.ExecuTorchLlamaDemo">
43+
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
3244
<intent-filter>
3345
<action android:name="android.intent.action.MAIN" />
3446

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
package com.example.executorchllamademo;
10+
11+
import java.text.SimpleDateFormat;
12+
import java.util.Date;
13+
import java.util.Locale;
14+
15+
public class AppLog {
16+
private final Long timestamp;
17+
private final String message;
18+
19+
public AppLog(String message) {
20+
this.timestamp = getCurrentTimeStamp();
21+
this.message = message;
22+
}
23+
24+
public Long getTimestamp() {
25+
return timestamp;
26+
}
27+
28+
public String getMessage() {
29+
return message;
30+
}
31+
32+
public String getFormattedLog() {
33+
return "[" + getFormattedTimeStamp() + "] " + message;
34+
}
35+
36+
private Long getCurrentTimeStamp() {
37+
return System.currentTimeMillis();
38+
}
39+
40+
private String getFormattedTimeStamp() {
41+
return formatDate(timestamp);
42+
}
43+
44+
private String formatDate(long milliseconds) {
45+
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
46+
Date date = new Date(milliseconds);
47+
return formatter.format(date);
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
package com.example.executorchllamademo;
10+
11+
import android.content.Context;
12+
import android.content.SharedPreferences;
13+
import com.google.gson.Gson;
14+
import com.google.gson.reflect.TypeToken;
15+
import java.lang.reflect.Type;
16+
import java.util.ArrayList;
17+
18+
public class DemoSharedPreferences {
19+
Context context;
20+
SharedPreferences sharedPreferences;
21+
22+
public DemoSharedPreferences(Context context) {
23+
this.context = context;
24+
this.sharedPreferences = getSharedPrefs();
25+
}
26+
27+
private SharedPreferences getSharedPrefs() {
28+
return context.getSharedPreferences(
29+
context.getString(R.string.demo_pref_file_key), Context.MODE_PRIVATE);
30+
}
31+
32+
public String getSavedMessages() {
33+
return sharedPreferences.getString(context.getString(R.string.saved_messages_json_key), "");
34+
}
35+
36+
public void addMessages(MessageAdapter messageAdapter) {
37+
SharedPreferences.Editor editor = sharedPreferences.edit();
38+
Gson gson = new Gson();
39+
String msgJSON = gson.toJson(messageAdapter.getSavedMessages());
40+
editor.putString(context.getString(R.string.saved_messages_json_key), msgJSON);
41+
editor.apply();
42+
}
43+
44+
public void removeExistingMessages() {
45+
SharedPreferences.Editor editor = sharedPreferences.edit();
46+
editor.remove(context.getString(R.string.saved_messages_json_key));
47+
editor.apply();
48+
}
49+
50+
public void addSettings(SettingsFields settingsFields) {
51+
SharedPreferences.Editor editor = sharedPreferences.edit();
52+
Gson gson = new Gson();
53+
String settingsJSON = gson.toJson(settingsFields);
54+
editor.putString(context.getString(R.string.settings_json_key), settingsJSON);
55+
editor.apply();
56+
}
57+
58+
public String getSettings() {
59+
return sharedPreferences.getString(context.getString(R.string.settings_json_key), "");
60+
}
61+
62+
public void saveLogs() {
63+
SharedPreferences.Editor editor = sharedPreferences.edit();
64+
Gson gson = new Gson();
65+
String msgJSON = gson.toJson(ETLogging.getInstance().getLogs());
66+
editor.putString(context.getString(R.string.logs_json_key), msgJSON);
67+
editor.apply();
68+
}
69+
70+
public void removeExistingLogs() {
71+
SharedPreferences.Editor editor = sharedPreferences.edit();
72+
editor.remove(context.getString(R.string.logs_json_key));
73+
editor.apply();
74+
}
75+
76+
public ArrayList<AppLog> getSavedLogs() {
77+
String logsJSONString =
78+
sharedPreferences.getString(context.getString(R.string.logs_json_key), null);
79+
if (logsJSONString == null || logsJSONString.isEmpty()) {
80+
return new ArrayList<>();
81+
}
82+
Gson gson = new Gson();
83+
Type type = new TypeToken<ArrayList<AppLog>>() {}.getType();
84+
ArrayList<AppLog> appLogs = gson.fromJson(logsJSONString, type);
85+
if (appLogs == null) {
86+
return new ArrayList<>();
87+
}
88+
return appLogs;
89+
}
90+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
package com.example.executorchllamademo;
10+
11+
import android.content.ContentResolver;
12+
import android.graphics.Bitmap;
13+
import android.graphics.BitmapFactory;
14+
import android.graphics.Color;
15+
import android.net.Uri;
16+
import androidx.annotation.Nullable;
17+
import java.io.FileNotFoundException;
18+
import java.io.InputStream;
19+
20+
public class ETImage {
21+
private int width;
22+
private int height;
23+
private final byte[] bytes;
24+
private final Uri uri;
25+
private final ContentResolver contentResolver;
26+
27+
ETImage(ContentResolver contentResolver, Uri uri) {
28+
this.contentResolver = contentResolver;
29+
this.uri = uri;
30+
bytes = getBytesFromImageURI(uri);
31+
}
32+
33+
public int getWidth() {
34+
return width;
35+
}
36+
37+
public int getHeight() {
38+
return height;
39+
}
40+
41+
public Uri getUri() {
42+
return uri;
43+
}
44+
45+
public byte[] getBytes() {
46+
return bytes;
47+
}
48+
49+
private byte[] getBytesFromImageURI(Uri uri) {
50+
try {
51+
int RESIZED_IMAGE_WIDTH = 336;
52+
Bitmap bitmap = resizeImage(uri, RESIZED_IMAGE_WIDTH);
53+
54+
if (bitmap == null) {
55+
ETLogging.getInstance().log("Unable to get bytes from Image URI. Bitmap is null");
56+
return new byte[0];
57+
}
58+
59+
width = bitmap.getWidth();
60+
height = bitmap.getHeight();
61+
62+
byte[] rgbValues = new byte[width * height * 3];
63+
64+
for (int y = 0; y < height; y++) {
65+
for (int x = 0; x < width; x++) {
66+
// Get the color of the current pixel
67+
int color = bitmap.getPixel(x, y);
68+
69+
// Extract the RGB values from the color
70+
int red = Color.red(color);
71+
int green = Color.green(color);
72+
int blue = Color.blue(color);
73+
74+
// Store the RGB values in the byte array
75+
rgbValues[(y * width + x) * 3] = (byte) red;
76+
rgbValues[(y * width + x) * 3 + 1] = (byte) green;
77+
rgbValues[(y * width + x) * 3 + 2] = (byte) blue;
78+
}
79+
}
80+
return rgbValues;
81+
} catch (FileNotFoundException e) {
82+
throw new RuntimeException(e);
83+
}
84+
}
85+
86+
@Nullable
87+
private Bitmap resizeImage(Uri uri, int maxLength) throws FileNotFoundException {
88+
InputStream inputStream = contentResolver.openInputStream(uri);
89+
if (inputStream == null) {
90+
ETLogging.getInstance().log("Unable to resize image, input streams is null");
91+
return null;
92+
}
93+
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
94+
if (bitmap == null) {
95+
ETLogging.getInstance().log("Unable to resize image, bitmap during decode stream is null");
96+
return null;
97+
}
98+
99+
float aspectRatio;
100+
int finalWidth, finalHeight;
101+
102+
if (bitmap.getWidth() > bitmap.getHeight()) {
103+
// width > height --> width = maxLength, height scale with aspect ratio
104+
aspectRatio = bitmap.getWidth() / (float) bitmap.getHeight();
105+
finalWidth = maxLength;
106+
finalHeight = Math.round(maxLength / aspectRatio);
107+
} else {
108+
// height >= width --> height = maxLength, width scale with aspect ratio
109+
aspectRatio = bitmap.getHeight() / (float) bitmap.getWidth();
110+
finalHeight = maxLength;
111+
finalWidth = Math.round(maxLength / aspectRatio);
112+
}
113+
114+
return Bitmap.createScaledBitmap(bitmap, finalWidth, finalHeight, false);
115+
}
116+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
package com.example.executorchllamademo;
10+
11+
import android.app.Application;
12+
import android.util.Log;
13+
import java.util.ArrayList;
14+
15+
public class ETLogging extends Application {
16+
private static ETLogging singleton;
17+
18+
private ArrayList<AppLog> logs;
19+
private DemoSharedPreferences mDemoSharedPreferences;
20+
21+
@Override
22+
public void onCreate() {
23+
super.onCreate();
24+
singleton = this;
25+
mDemoSharedPreferences = new DemoSharedPreferences(this.getApplicationContext());
26+
logs = mDemoSharedPreferences.getSavedLogs();
27+
if (logs == null) { // We don't have existing sharedPreference stored
28+
logs = new ArrayList<>();
29+
}
30+
}
31+
32+
public static ETLogging getInstance() {
33+
return singleton;
34+
}
35+
36+
public void log(String message) {
37+
AppLog appLog = new AppLog(message);
38+
logs.add(appLog);
39+
Log.d("ETLogging", appLog.getMessage());
40+
}
41+
42+
public ArrayList<AppLog> getLogs() {
43+
return logs;
44+
}
45+
46+
public void clearLogs() {
47+
logs.clear();
48+
mDemoSharedPreferences.removeExistingLogs();
49+
}
50+
51+
public void saveLogs() {
52+
mDemoSharedPreferences.saveLogs();
53+
}
54+
}

0 commit comments

Comments
 (0)