From 4036bb8965ade3c58e2fbd5130f1a009a843bfe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=A5=E9=81=97?= Date: Thu, 26 Mar 2026 13:18:53 +0800 Subject: [PATCH] [MNNChat:Bugfix] reuse loaded runtime session for API start in 0.8.2.2 --- apps/Android/MnnLlmChat/README.md | 11 +++++ apps/Android/MnnLlmChat/README_CN.md | 11 +++++ apps/Android/MnnLlmChat/app/build.gradle | 4 +- .../service/ApiRuntimeSessionStartPolicy.kt | 14 ++++++ .../openai/service/ApiServiceCoordinator.kt | 43 ++++++++++++------- .../ApiRuntimeSessionStartPolicyTest.kt | 41 ++++++++++++++++++ 6 files changed, 106 insertions(+), 18 deletions(-) create mode 100644 apps/Android/MnnLlmChat/app/src/main/java/com/alibaba/mnnllm/api/openai/service/ApiRuntimeSessionStartPolicy.kt create mode 100644 apps/Android/MnnLlmChat/app/src/test/java/com/alibaba/mnnllm/api/openai/service/ApiRuntimeSessionStartPolicyTest.kt diff --git a/apps/Android/MnnLlmChat/README.md b/apps/Android/MnnLlmChat/README.md index 0eabb4b567..41eb3bd576 100644 --- a/apps/Android/MnnLlmChat/README.md +++ b/apps/Android/MnnLlmChat/README.md @@ -62,6 +62,17 @@ This is our full multimodal language model (LLM) Android app # Releases +## Version 0.8.2.2 ++ Click here to [download](https://meta.alicdn.com/data/mnn/apks/mnn_chat_0_8_2_2.apk) ++ Highlights: + + Refresh the bundled MNN runtime with the latest CPU LinearAttention and Arm82 fp16 optimization path. + + Improve tokenizer and template rendering compatibility for thinking-mode prompts and array concatenation cases. + + Add TopKV2 backend coverage for OpenCL and Metal execution paths. ++ Bugfix: + + Fix the crash when tapping Add Local Model on Android. + + Avoid deadlocks caused by partially initialized mmap weights during model loading. + + Reuse the loaded runtime session when starting API service for the same model to avoid extra reloads that could freeze or crash API startup. + ## Version 0.8.2.1 + Click here to [download](https://meta.alicdn.com/data/mnn/apks/mnn_chat_0_8_2_1.apk) + Bugfix: diff --git a/apps/Android/MnnLlmChat/README_CN.md b/apps/Android/MnnLlmChat/README_CN.md index 9e5e498a42..27ef626570 100644 --- a/apps/Android/MnnLlmChat/README_CN.md +++ b/apps/Android/MnnLlmChat/README_CN.md @@ -58,6 +58,17 @@ ``` # Releases +## Version 0.8.2.2 ++ 点击这里 [下载](https://meta.alicdn.com/data/mnn/apks/mnn_chat_0_8_2_2.apk) ++ 更新亮点: + + 刷新内置 MNN runtime,带入最新的 CPU LinearAttention 与 Arm82 fp16 优化路径。 + + 提升思考模式提示词和数组拼接场景下的 tokenizer 与模板渲染兼容性。 + + 为 OpenCL 和 Metal 执行路径补充 TopKV2 后端支持。 ++ 问题修复: + + 修复 Android 点击 Add Local Model 时的崩溃问题。 + + 避免模型加载时因 mmap 权重部分初始化而导致的死锁问题。 + + 对同模型启动 API 服务时复用已加载 runtime session,避免额外重载导致的卡死或崩溃。 + ## Version 0.8.2.1 + 点击这里 [下载](https://meta.alicdn.com/data/mnn/apks/mnn_chat_0_8_2_1.apk) + 问题修复: diff --git a/apps/Android/MnnLlmChat/app/build.gradle b/apps/Android/MnnLlmChat/app/build.gradle index 5763fb18b8..f0c20c4f2e 100644 --- a/apps/Android/MnnLlmChat/app/build.gradle +++ b/apps/Android/MnnLlmChat/app/build.gradle @@ -69,8 +69,8 @@ android { applicationId "com.alibaba.mnnllm.android" minSdk 26 targetSdk 35 - versionCode 821 - versionName "0.8.2.1" + versionCode 822 + versionName "0.8.2.2" buildConfigField "boolean", "ENABLE_FIREBASE", enableFirebase ? "true" : "false" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { diff --git a/apps/Android/MnnLlmChat/app/src/main/java/com/alibaba/mnnllm/api/openai/service/ApiRuntimeSessionStartPolicy.kt b/apps/Android/MnnLlmChat/app/src/main/java/com/alibaba/mnnllm/api/openai/service/ApiRuntimeSessionStartPolicy.kt new file mode 100644 index 0000000000..2176cf92cc --- /dev/null +++ b/apps/Android/MnnLlmChat/app/src/main/java/com/alibaba/mnnllm/api/openai/service/ApiRuntimeSessionStartPolicy.kt @@ -0,0 +1,14 @@ +package com.alibaba.mnnllm.api.openai.service + +internal object ApiRuntimeSessionStartPolicy { + fun shouldReuseLoadedSession( + requestedModelId: String?, + activeModelId: String?, + hasLoadedActiveSession: Boolean + ): Boolean { + if (!hasLoadedActiveSession) { + return false + } + return requestedModelId.isNullOrBlank() || requestedModelId == activeModelId + } +} diff --git a/apps/Android/MnnLlmChat/app/src/main/java/com/alibaba/mnnllm/api/openai/service/ApiServiceCoordinator.kt b/apps/Android/MnnLlmChat/app/src/main/java/com/alibaba/mnnllm/api/openai/service/ApiServiceCoordinator.kt index dad2102c20..07e2ff8772 100644 --- a/apps/Android/MnnLlmChat/app/src/main/java/com/alibaba/mnnllm/api/openai/service/ApiServiceCoordinator.kt +++ b/apps/Android/MnnLlmChat/app/src/main/java/com/alibaba/mnnllm/api/openai/service/ApiServiceCoordinator.kt @@ -57,7 +57,7 @@ class ApiServiceCoordinator(private val context: Context) { if (application != null && _isServerRunning) { if (!modelId.isNullOrBlank()) { - val switchedSession = ensureRuntimeSessionForModel(modelId) + val switchedSession = resolveRuntimeSessionForStart(modelId) if (switchedSession == null) { notificationManager?.updateNotification( context.getString(R.string.api_service_not_started), @@ -71,21 +71,7 @@ class ApiServiceCoordinator(private val context: Context) { } return runCatching { - val runtimeSession = if (!modelId.isNullOrBlank()) { - val session = ensureRuntimeSessionForModel(modelId) - if (session == null) { - notificationManager?.updateNotification( - context.getString(R.string.api_service_not_started), - context.getString(R.string.no_active_session) - ) - return false - } - session - } else { - ServiceLocator.getLlmRuntimeController().getActiveSession() - } - - val session = runtimeSession ?: ServiceLocator.getChatSessionProvider().getLlmSession() + val session = resolveRuntimeSessionForStart(modelId) if (session == null) { Timber.Forest.tag(TAG).w("No active LlmSession found") notificationManager?.updateNotification( @@ -125,6 +111,31 @@ class ApiServiceCoordinator(private val context: Context) { } } + private fun resolveRuntimeSessionForStart(modelId: String?): LlmSession? { + val runtime = ServiceLocator.getLlmRuntimeController() + val chatSessionProvider = ServiceLocator.getChatSessionProvider() + val activeModelId = runtime.getActiveModelId() + val hasLoadedActiveSession = chatSessionProvider.hasActiveSession() + + if (ApiRuntimeSessionStartPolicy.shouldReuseLoadedSession(modelId, activeModelId, hasLoadedActiveSession)) { + val activeSession = runtime.getActiveSession() ?: chatSessionProvider.getLlmSession() + if (activeSession != null) { + Timber.Forest.tag(TAG).i( + "Reusing loaded runtime session for API start, requestedModelId=%s activeModelId=%s", + modelId, + activeModelId + ) + return activeSession + } + } + + if (!modelId.isNullOrBlank()) { + return ensureRuntimeSessionForModel(modelId) + } + + return runtime.getActiveSession() ?: chatSessionProvider.getLlmSession() + } + private fun ensureRuntimeSessionForModel(modelId: String): LlmSession? { val ensureResult = ServiceLocator.getLlmRuntimeController().ensureSession(modelId) if (!ensureResult.success || ensureResult.session == null) { diff --git a/apps/Android/MnnLlmChat/app/src/test/java/com/alibaba/mnnllm/api/openai/service/ApiRuntimeSessionStartPolicyTest.kt b/apps/Android/MnnLlmChat/app/src/test/java/com/alibaba/mnnllm/api/openai/service/ApiRuntimeSessionStartPolicyTest.kt new file mode 100644 index 0000000000..e901b40525 --- /dev/null +++ b/apps/Android/MnnLlmChat/app/src/test/java/com/alibaba/mnnllm/api/openai/service/ApiRuntimeSessionStartPolicyTest.kt @@ -0,0 +1,41 @@ +package com.alibaba.mnnllm.api.openai.service + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class ApiRuntimeSessionStartPolicyTest { + + @Test + fun `same model with loaded active session should reuse runtime session`() { + assertTrue( + ApiRuntimeSessionStartPolicy.shouldReuseLoadedSession( + requestedModelId = "ModelScope/MNN/Qwen3.5-0.8B-MNN", + activeModelId = "ModelScope/MNN/Qwen3.5-0.8B-MNN", + hasLoadedActiveSession = true + ) + ) + } + + @Test + fun `different model should not reuse runtime session`() { + assertFalse( + ApiRuntimeSessionStartPolicy.shouldReuseLoadedSession( + requestedModelId = "ModelScope/MNN/Qwen3.5-2B-MNN", + activeModelId = "ModelScope/MNN/Qwen3.5-0.8B-MNN", + hasLoadedActiveSession = true + ) + ) + } + + @Test + fun `missing loaded session should not reuse runtime session`() { + assertFalse( + ApiRuntimeSessionStartPolicy.shouldReuseLoadedSession( + requestedModelId = "ModelScope/MNN/Qwen3.5-0.8B-MNN", + activeModelId = "ModelScope/MNN/Qwen3.5-0.8B-MNN", + hasLoadedActiveSession = false + ) + ) + } +}