From 1d7ef77adfaa5098c2bd0aa1db71ce97d42695d2 Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:00:39 +0200 Subject: [PATCH 01/10] Fix LiteRT native loading fallback for Gemma 4 --- .../feature/multimodal/PhotoReasoningViewModel.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt index d816929..1b644f6 100644 --- a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt +++ b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt @@ -57,6 +57,7 @@ import com.google.ai.sample.ApiProvider import com.google.ai.edge.litertlm.Backend import com.google.ai.edge.litertlm.Engine import com.google.ai.edge.litertlm.EngineConfig +import com.google.ai.edge.litertlm.NativeLibraryLoader import com.google.mediapipe.tasks.genai.llminference.LlmInference import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient @@ -86,6 +87,7 @@ class PhotoReasoningViewModel( private var llmInference: LlmInference? = null private var liteRtEngine: Engine? = null + private var liteRtNativeLoaded = false private val TAG = "PhotoReasoningViewModel" // WebRTC & Signaling @@ -337,6 +339,7 @@ class PhotoReasoningViewModel( if (!isLiteRtAbiSupported()) { return "Gemma 4 offline is only supported on arm64-v8a or x86_64 devices." } + ensureLiteRtNativeLoaded() if (liteRtEngine == null) { val liteRtBackend = if (backend == InferenceBackend.GPU) Backend.GPU else Backend.CPU val engineConfig = EngineConfig( @@ -388,6 +391,18 @@ class PhotoReasoningViewModel( } } + private fun ensureLiteRtNativeLoaded() { + if (liteRtNativeLoaded) return + + runCatching { + NativeLibraryLoader.INSTANCE.load() + }.recoverCatching { + System.loadLibrary("litertlm_jni") + }.getOrThrow() + + liteRtNativeLoaded = true + } + private fun isLiteRtAbiSupported(): Boolean { val supportedAbis = Build.SUPPORTED_ABIS?.toSet().orEmpty() return supportedAbis.contains("arm64-v8a") || supportedAbis.contains("x86_64") From 28f5bac7aca65f4853edad96b247964e0504d3e4 Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:11:01 +0200 Subject: [PATCH 02/10] Fix internal LiteRT loader access error --- .../sample/feature/multimodal/PhotoReasoningViewModel.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt index 1b644f6..40d696f 100644 --- a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt +++ b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt @@ -57,7 +57,6 @@ import com.google.ai.sample.ApiProvider import com.google.ai.edge.litertlm.Backend import com.google.ai.edge.litertlm.Engine import com.google.ai.edge.litertlm.EngineConfig -import com.google.ai.edge.litertlm.NativeLibraryLoader import com.google.mediapipe.tasks.genai.llminference.LlmInference import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient @@ -394,11 +393,7 @@ class PhotoReasoningViewModel( private fun ensureLiteRtNativeLoaded() { if (liteRtNativeLoaded) return - runCatching { - NativeLibraryLoader.INSTANCE.load() - }.recoverCatching { - System.loadLibrary("litertlm_jni") - }.getOrThrow() + System.loadLibrary("litertlm_jni") liteRtNativeLoaded = true } From c2ff8f42e15cf40d00827d87c5818e0064c2820e Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:36:57 +0200 Subject: [PATCH 03/10] Force CPU backend for Gemma 4 LiteRT stability --- .../multimodal/PhotoReasoningViewModel.kt | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt index 40d696f..45b7415 100644 --- a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt +++ b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt @@ -340,14 +340,20 @@ class PhotoReasoningViewModel( } ensureLiteRtNativeLoaded() if (liteRtEngine == null) { - val liteRtBackend = if (backend == InferenceBackend.GPU) Backend.GPU else Backend.CPU + val liteRtBackend = Backend.CPU + if (backend == InferenceBackend.GPU) { + Log.w( + TAG, + "Gemma 4 offline currently forces CPU backend to avoid native crashes on GPU initialization." + ) + } val engineConfig = EngineConfig( modelPath = modelFile.absolutePath, backend = liteRtBackend, cacheDir = context.cacheDir.absolutePath ) liteRtEngine = Engine(engineConfig).also { it.initialize() } - Log.d(TAG, "Offline model initialized with LiteRT-LM Engine backend=$backend") + Log.d(TAG, "Offline model initialized with LiteRT-LM Engine backend=$liteRtBackend") } } else { if (llmInference == null) { @@ -456,7 +462,12 @@ class PhotoReasoningViewModel( } private fun isOfflineGpuModelLoaded(): Boolean { - return com.google.ai.sample.GenerativeAiViewModelFactory.getCurrentModel().isOfflineModel && + val currentModel = com.google.ai.sample.GenerativeAiViewModelFactory.getCurrentModel() + if (currentModel == ModelOption.GEMMA_4_E4B_IT) { + // Gemma 4 offline currently runs with CPU backend for stability. + return false + } + return currentModel.isOfflineModel && com.google.ai.sample.GenerativeAiViewModelFactory.getBackend() == InferenceBackend.GPU && (llmInference != null || liteRtEngine != null) } From 6fb9a4091835e9864488562c9d3744f953ed436c Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:37:01 +0200 Subject: [PATCH 04/10] Restore Gemma4 backend choice and add init diagnostics --- .../multimodal/PhotoReasoningViewModel.kt | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt index 45b7415..8f8d3a7 100644 --- a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt +++ b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt @@ -338,20 +338,24 @@ class PhotoReasoningViewModel( if (!isLiteRtAbiSupported()) { return "Gemma 4 offline is only supported on arm64-v8a or x86_64 devices." } + Log.i( + TAG, + "Initializing Gemma 4 LiteRT engine. preferredBackend=$backend, " + + "abis=${Build.SUPPORTED_ABIS?.joinToString() ?: "unknown"}, " + + "modelPath=${modelFile.absolutePath}, modelSizeBytes=${modelFile.length()}" + ) ensureLiteRtNativeLoaded() if (liteRtEngine == null) { - val liteRtBackend = Backend.CPU - if (backend == InferenceBackend.GPU) { - Log.w( - TAG, - "Gemma 4 offline currently forces CPU backend to avoid native crashes on GPU initialization." - ) - } + val liteRtBackend = if (backend == InferenceBackend.GPU) Backend.GPU else Backend.CPU val engineConfig = EngineConfig( modelPath = modelFile.absolutePath, backend = liteRtBackend, cacheDir = context.cacheDir.absolutePath ) + Log.i( + TAG, + "Creating LiteRT engine with backend=$liteRtBackend cacheDir=${context.cacheDir.absolutePath}" + ) liteRtEngine = Engine(engineConfig).also { it.initialize() } Log.d(TAG, "Offline model initialized with LiteRT-LM Engine backend=$liteRtBackend") } @@ -381,6 +385,12 @@ class PhotoReasoningViewModel( return null // Already initialized or no model file } catch (e: Exception) { Log.e(TAG, "Failed to initialize offline model", e) + Log.e( + TAG, + "Offline init context: model=${com.google.ai.sample.GenerativeAiViewModelFactory.getCurrentModel()}, " + + "preferredBackend=${GenerativeAiViewModelFactory.getBackend()}, " + + "abis=${Build.SUPPORTED_ABIS?.joinToString() ?: "unknown"}" + ) val msg = e.message ?: e.toString() if (msg.contains("nativeCheckLoaded", ignoreCase = true) || msg.contains("No implementation found", ignoreCase = true) || @@ -462,12 +472,7 @@ class PhotoReasoningViewModel( } private fun isOfflineGpuModelLoaded(): Boolean { - val currentModel = com.google.ai.sample.GenerativeAiViewModelFactory.getCurrentModel() - if (currentModel == ModelOption.GEMMA_4_E4B_IT) { - // Gemma 4 offline currently runs with CPU backend for stability. - return false - } - return currentModel.isOfflineModel && + return com.google.ai.sample.GenerativeAiViewModelFactory.getCurrentModel().isOfflineModel && com.google.ai.sample.GenerativeAiViewModelFactory.getBackend() == InferenceBackend.GPU && (llmInference != null || liteRtEngine != null) } From 84d9f3afc7d6457f173eb163d45509040810f907 Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:53:02 +0200 Subject: [PATCH 05/10] Set explicit CPU vision/audio backends for Gemma4 LiteRT --- .../sample/feature/multimodal/PhotoReasoningViewModel.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt index 8f8d3a7..7b4a132 100644 --- a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt +++ b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt @@ -347,14 +347,21 @@ class PhotoReasoningViewModel( ensureLiteRtNativeLoaded() if (liteRtEngine == null) { val liteRtBackend = if (backend == InferenceBackend.GPU) Backend.GPU else Backend.CPU + val visionBackend = Backend.CPU + val audioBackend = Backend.CPU val engineConfig = EngineConfig( modelPath = modelFile.absolutePath, backend = liteRtBackend, + visionBackend = visionBackend, + audioBackend = audioBackend, + maxNumTokens = null, cacheDir = context.cacheDir.absolutePath ) Log.i( TAG, - "Creating LiteRT engine with backend=$liteRtBackend cacheDir=${context.cacheDir.absolutePath}" + "Creating LiteRT engine with backend=$liteRtBackend, " + + "visionBackend=$visionBackend, audioBackend=$audioBackend, " + + "cacheDir=${context.cacheDir.absolutePath}" ) liteRtEngine = Engine(engineConfig).also { it.initialize() } Log.d(TAG, "Offline model initialized with LiteRT-LM Engine backend=$liteRtBackend") From 7a643adc85a99a74d51e4eeb069214657e55c02a Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:36:37 +0200 Subject: [PATCH 06/10] Align LiteRT integration with AI Edge Gallery setup --- app/build.gradle.kts | 2 +- .../multimodal/PhotoReasoningViewModel.kt | 16 +++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7ee2a33..4df88cd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -111,7 +111,7 @@ dependencies { // MediaPipe GenAI for offline inference (LLM) implementation("com.google.mediapipe:tasks-genai:0.10.32") // LiteRT-LM for newer offline .litertlm models (e.g. Gemma 4 E4B it) - implementation("com.google.ai.edge.litertlm:litertlm-android:0.0.0-alpha06") + implementation("com.google.ai.edge.litertlm:litertlm-android:0.10.0") // Camera Core to potentially fix missing JNI lib issue implementation("androidx.camera:camera-core:1.4.0") diff --git a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt index 7b4a132..27918f4 100644 --- a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt +++ b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt @@ -86,7 +86,6 @@ class PhotoReasoningViewModel( private var llmInference: LlmInference? = null private var liteRtEngine: Engine? = null - private var liteRtNativeLoaded = false private val TAG = "PhotoReasoningViewModel" // WebRTC & Signaling @@ -344,11 +343,10 @@ class PhotoReasoningViewModel( "abis=${Build.SUPPORTED_ABIS?.joinToString() ?: "unknown"}, " + "modelPath=${modelFile.absolutePath}, modelSizeBytes=${modelFile.length()}" ) - ensureLiteRtNativeLoaded() if (liteRtEngine == null) { - val liteRtBackend = if (backend == InferenceBackend.GPU) Backend.GPU else Backend.CPU - val visionBackend = Backend.CPU - val audioBackend = Backend.CPU + val liteRtBackend = if (backend == InferenceBackend.GPU) Backend.GPU() else Backend.CPU() + val visionBackend = if (currentModel.supportsScreenshot) Backend.GPU() else null + val audioBackend = null val engineConfig = EngineConfig( modelPath = modelFile.absolutePath, backend = liteRtBackend, @@ -413,14 +411,6 @@ class PhotoReasoningViewModel( } } - private fun ensureLiteRtNativeLoaded() { - if (liteRtNativeLoaded) return - - System.loadLibrary("litertlm_jni") - - liteRtNativeLoaded = true - } - private fun isLiteRtAbiSupported(): Boolean { val supportedAbis = Build.SUPPORTED_ABIS?.toSet().orEmpty() return supportedAbis.contains("arm64-v8a") || supportedAbis.contains("x86_64") From 9a5afecd0e25e74c9527121da65b90317079f921 Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:58:23 +0200 Subject: [PATCH 07/10] Use Java 11-compatible LiteRT-LM release --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4df88cd..90303aa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -111,7 +111,7 @@ dependencies { // MediaPipe GenAI for offline inference (LLM) implementation("com.google.mediapipe:tasks-genai:0.10.32") // LiteRT-LM for newer offline .litertlm models (e.g. Gemma 4 E4B it) - implementation("com.google.ai.edge.litertlm:litertlm-android:0.10.0") + implementation("com.google.ai.edge.litertlm:litertlm-android:0.9.0-alpha06") // Camera Core to potentially fix missing JNI lib issue implementation("androidx.camera:camera-core:1.4.0") From f8be311c5b4bc9a1e99ffa5dcde573dac260434a Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:58:27 +0200 Subject: [PATCH 08/10] Upgrade Android build toolchain for LiteRT 0.10 dexing --- app/build.gradle.kts | 2 +- build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 90303aa..4df88cd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -111,7 +111,7 @@ dependencies { // MediaPipe GenAI for offline inference (LLM) implementation("com.google.mediapipe:tasks-genai:0.10.32") // LiteRT-LM for newer offline .litertlm models (e.g. Gemma 4 E4B it) - implementation("com.google.ai.edge.litertlm:litertlm-android:0.9.0-alpha06") + implementation("com.google.ai.edge.litertlm:litertlm-android:0.10.0") // Camera Core to potentially fix missing JNI lib issue implementation("androidx.camera:camera-core:1.4.0") diff --git a/build.gradle.kts b/build.gradle.kts index a4a00dd..d134f4e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,7 +20,7 @@ buildscript { } // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.1.3" apply false + id("com.android.application") version "8.8.2" apply false id("org.jetbrains.kotlin.android") version "1.9.20" apply false id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") version "2.0.1" apply false id("com.google.gms.google-services") version "4.4.2" apply false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e411586..1e2fbf0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 02918f9da424ef0324df078700043f1430b61124 Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:46:41 +0200 Subject: [PATCH 09/10] Align Gemma4 config with gallery multimodal backend setup --- .../google/ai/sample/GenerativeAiViewModelFactory.kt | 1 + .../feature/multimodal/PhotoReasoningViewModel.kt | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt b/app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt index 42215a9..82398e0 100644 --- a/app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt +++ b/app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt @@ -60,6 +60,7 @@ enum class ModelOption( "gemma-4-e4b-it", ApiProvider.GOOGLE, "https://huggingface.co/litert-community/gemma-4-E4B-it-litert-lm/resolve/main/gemma-4-E4B-it.litertlm?download=true", + supportsScreenshot = false, isOfflineModel = true, offlineModelFilename = "gemma-4-E4B-it.litertlm" ), diff --git a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt index 27918f4..c024703 100644 --- a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt +++ b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt @@ -345,21 +345,27 @@ class PhotoReasoningViewModel( ) if (liteRtEngine == null) { val liteRtBackend = if (backend == InferenceBackend.GPU) Backend.GPU() else Backend.CPU() - val visionBackend = if (currentModel.supportsScreenshot) Backend.GPU() else null + val visionBackend = if (currentModel.supportsScreenshot) Backend.CPU() else null val audioBackend = null + val cacheDir = + if (modelFile.absolutePath.startsWith("/data/local/tmp")) { + context.getExternalFilesDir(null)?.absolutePath + } else { + null + } val engineConfig = EngineConfig( modelPath = modelFile.absolutePath, backend = liteRtBackend, visionBackend = visionBackend, audioBackend = audioBackend, maxNumTokens = null, - cacheDir = context.cacheDir.absolutePath + cacheDir = cacheDir ) Log.i( TAG, "Creating LiteRT engine with backend=$liteRtBackend, " + "visionBackend=$visionBackend, audioBackend=$audioBackend, " + - "cacheDir=${context.cacheDir.absolutePath}" + "cacheDir=$cacheDir" ) liteRtEngine = Engine(engineConfig).also { it.initialize() } Log.d(TAG, "Offline model initialized with LiteRT-LM Engine backend=$liteRtBackend") From 317189719505c6e86bab9bf526ef7b64e2810098 Mon Sep 17 00:00:00 2001 From: Android PowerUser <88908510+Android-PowerUser@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:46:44 +0200 Subject: [PATCH 10/10] Restore Gemma4 screenshots and add LiteRT backend fallbacks --- .../ai/sample/GenerativeAiViewModelFactory.kt | 1 - .../multimodal/PhotoReasoningViewModel.kt | 77 +++++++++++++++---- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt b/app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt index 82398e0..42215a9 100644 --- a/app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt +++ b/app/src/main/kotlin/com/google/ai/sample/GenerativeAiViewModelFactory.kt @@ -60,7 +60,6 @@ enum class ModelOption( "gemma-4-e4b-it", ApiProvider.GOOGLE, "https://huggingface.co/litert-community/gemma-4-E4B-it-litert-lm/resolve/main/gemma-4-E4B-it.litertlm?download=true", - supportsScreenshot = false, isOfflineModel = true, offlineModelFilename = "gemma-4-E4B-it.litertlm" ), diff --git a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt index c024703..a059839 100644 --- a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt +++ b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningViewModel.kt @@ -344,8 +344,8 @@ class PhotoReasoningViewModel( "modelPath=${modelFile.absolutePath}, modelSizeBytes=${modelFile.length()}" ) if (liteRtEngine == null) { - val liteRtBackend = if (backend == InferenceBackend.GPU) Backend.GPU() else Backend.CPU() - val visionBackend = if (currentModel.supportsScreenshot) Backend.CPU() else null + val preferredBackend = if (backend == InferenceBackend.GPU) Backend.GPU() else Backend.CPU() + val preferredVisionBackend = if (currentModel.supportsScreenshot) Backend.GPU() else null val audioBackend = null val cacheDir = if (modelFile.absolutePath.startsWith("/data/local/tmp")) { @@ -353,22 +353,14 @@ class PhotoReasoningViewModel( } else { null } - val engineConfig = EngineConfig( + liteRtEngine = createLiteRtEngineWithFallbacks( modelPath = modelFile.absolutePath, - backend = liteRtBackend, - visionBackend = visionBackend, + preferredBackend = preferredBackend, + preferredVisionBackend = preferredVisionBackend, audioBackend = audioBackend, - maxNumTokens = null, cacheDir = cacheDir ) - Log.i( - TAG, - "Creating LiteRT engine with backend=$liteRtBackend, " + - "visionBackend=$visionBackend, audioBackend=$audioBackend, " + - "cacheDir=$cacheDir" - ) - liteRtEngine = Engine(engineConfig).also { it.initialize() } - Log.d(TAG, "Offline model initialized with LiteRT-LM Engine backend=$liteRtBackend") + Log.d(TAG, "Offline model initialized with LiteRT-LM Engine") } } else { if (llmInference == null) { @@ -421,6 +413,63 @@ class PhotoReasoningViewModel( val supportedAbis = Build.SUPPORTED_ABIS?.toSet().orEmpty() return supportedAbis.contains("arm64-v8a") || supportedAbis.contains("x86_64") } + + private fun createLiteRtEngineWithFallbacks( + modelPath: String, + preferredBackend: Backend, + preferredVisionBackend: Backend?, + audioBackend: Backend?, + cacheDir: String? + ): Engine { + val cpuBackend = Backend.CPU() + val gpuBackend = Backend.GPU() + val attempts = linkedSetOf( + preferredBackend to preferredVisionBackend, + cpuBackend to preferredVisionBackend, + cpuBackend to cpuBackend, + gpuBackend to cpuBackend + ) + var lastError: Exception? = null + val failureDetails = StringBuilder() + + attempts.forEachIndexed { index, (backendAttempt, visionAttempt) -> + try { + Log.i( + TAG, + "LiteRT init attempt ${index + 1}/${attempts.size}: " + + "backend=$backendAttempt visionBackend=$visionAttempt audioBackend=$audioBackend cacheDir=$cacheDir" + ) + val config = EngineConfig( + modelPath = modelPath, + backend = backendAttempt, + visionBackend = visionAttempt, + audioBackend = audioBackend, + maxNumTokens = null, + cacheDir = cacheDir + ) + return Engine(config).also { it.initialize() } + } catch (e: Exception) { + lastError = e + val msg = e.message ?: e.toString() + failureDetails + .append("Attempt ") + .append(index + 1) + .append(" failed (backend=") + .append(backendAttempt) + .append(", visionBackend=") + .append(visionAttempt) + .append("): ") + .append(msg) + .append('\n') + Log.w(TAG, "LiteRT init attempt ${index + 1} failed", e) + } + } + + throw IllegalStateException( + "All LiteRT initialization attempts failed.\n$failureDetails", + lastError + ) + } fun reinitializeOfflineModel(context: Context) { viewModelScope.launch(Dispatchers.IO) {