Skip to content
Merged
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
11 changes: 11 additions & 0 deletions apps/Android/MnnLlmChat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
11 changes: 11 additions & 0 deletions apps/Android/MnnLlmChat/README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
+ 问题修复:
Expand Down
4 changes: 2 additions & 2 deletions apps/Android/MnnLlmChat/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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(
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
)
)
}
}
Loading