Skip to content

Commit 0af259c

Browse files
Merge origin/main into smonga/web_updates
Resolved conflicts in llama.cpp backend: - VERSIONS: take main's b8201 (newer than b8179) - CMakeLists.txt: deduplicate mtmd model source files - llamacpp_backend.cpp: adopt main's simplified LoRA handling Made-with: Cursor
2 parents 3a8d798 + 0b82551 commit 0af259c

40 files changed

Lines changed: 1714 additions & 1266 deletions

examples/android/RunAnywhereAI/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
<activity
4141
android:name=".MainActivity"
4242
android:exported="true"
43-
android:windowSoftInputMode="adjustResize"
43+
android:windowSoftInputMode="adjustNothing"
4444
android:theme="@style/Theme.RunAnywhereAI">
4545
<intent-filter>
4646
<action android:name="android.intent.action.MAIN" />

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/RunAnywhereApplication.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,12 @@ class RunAnywhereApplication : Application() {
208208
isSDKInitialized = RunAnywhere.isInitialized
209209

210210
// Update observable state for Compose UI
211+
val error = initializationError
211212
if (isSDKInitialized) {
212213
_initializationState.value = SDKInitializationState.Ready
213214
Timber.i("🎉 App is ready to use!")
214-
} else if (initializationError != null) {
215-
_initializationState.value = SDKInitializationState.Error(initializationError!!)
215+
} else if (error != null) {
216+
_initializationState.value = SDKInitializationState.Error(error)
216217
} else {
217218
// SDK reported not initialized but no error - treat as ready for offline mode
218219
_initializationState.value = SDKInitializationState.Ready

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/data/ConversationStore.kt

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ import timber.log.Timber
66
import com.runanywhere.runanywhereai.domain.models.ChatMessage
77
import com.runanywhere.runanywhereai.domain.models.Conversation
88
import com.runanywhere.runanywhereai.domain.models.MessageRole
9+
import kotlinx.coroutines.CoroutineScope
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.SupervisorJob
912
import kotlinx.coroutines.flow.MutableStateFlow
1013
import kotlinx.coroutines.flow.StateFlow
1114
import kotlinx.coroutines.flow.asStateFlow
15+
import kotlinx.coroutines.flow.update
16+
import kotlinx.coroutines.launch
1217
import kotlinx.serialization.decodeFromString
1318
import kotlinx.serialization.encodeToString
1419
import kotlinx.serialization.json.Json
@@ -42,6 +47,7 @@ class ConversationStore private constructor(context: Context) {
4247
val currentConversation: StateFlow<Conversation?> = _currentConversation.asStateFlow()
4348

4449
private val conversationsDirectory: File
50+
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
4551
private val json =
4652
Json {
4753
prettyPrint = true
@@ -53,7 +59,7 @@ class ConversationStore private constructor(context: Context) {
5359
if (!conversationsDirectory.exists()) {
5460
conversationsDirectory.mkdirs()
5561
}
56-
loadConversations()
62+
ioScope.launch { loadConversations() }
5763
}
5864

5965
// MARK: - Public Methods
@@ -74,9 +80,7 @@ class ConversationStore private constructor(context: Context) {
7480
performanceSummary = null,
7581
)
7682

77-
val updated = _conversations.value.toMutableList()
78-
updated.add(0, conversation)
79-
_conversations.value = updated
83+
_conversations.update { list -> listOf(conversation) + list }
8084
_currentConversation.value = conversation
8185

8286
saveConversation(conversation)
@@ -88,31 +92,36 @@ class ConversationStore private constructor(context: Context) {
8892
* If not present (by id), adds it at the front so it appears in history.
8993
*/
9094
fun ensureConversationInList(conversation: Conversation) {
91-
val index = _conversations.value.indexOfFirst { it.id == conversation.id }
92-
if (index == -1) {
93-
val list = _conversations.value.toMutableList()
94-
list.add(0, conversation)
95-
_conversations.value = list
96-
saveConversation(conversation)
95+
var wasAdded = false
96+
_conversations.update { list ->
97+
if (list.any { it.id == conversation.id }) {
98+
list
99+
} else {
100+
wasAdded = true
101+
listOf(conversation) + list
102+
}
97103
}
104+
if (wasAdded) saveConversation(conversation)
98105
}
99106

100107
/**
101108
* Update an existing conversation
102109
*/
103110
fun updateConversation(conversation: Conversation) {
104111
val updated = conversation.copy(updatedAt = System.currentTimeMillis())
105-
106-
val index = _conversations.value.indexOfFirst { it.id == conversation.id }
107-
if (index != -1) {
108-
val list = _conversations.value.toMutableList()
109-
list[index] = updated
110-
_conversations.value = list
111-
112+
var found = false
113+
_conversations.update { list ->
114+
list.map {
115+
if (it.id == conversation.id) {
116+
found = true
117+
updated
118+
} else it
119+
}
120+
}
121+
if (found) {
112122
if (_currentConversation.value?.id == conversation.id) {
113123
_currentConversation.value = updated
114124
}
115-
116125
saveConversation(updated)
117126
}
118127
}
@@ -121,16 +130,18 @@ class ConversationStore private constructor(context: Context) {
121130
* Delete a conversation
122131
*/
123132
fun deleteConversation(conversation: Conversation) {
124-
_conversations.value = _conversations.value.filter { it.id != conversation.id }
133+
_conversations.update { list -> list.filter { it.id != conversation.id } }
125134

126135
if (_currentConversation.value?.id == conversation.id) {
127136
_currentConversation.value = _conversations.value.firstOrNull()
128137
}
129138

130-
// Delete file
131-
val file = conversationFileURL(conversation.id)
132-
if (file.exists()) {
133-
file.delete()
139+
// Delete file off main thread
140+
ioScope.launch {
141+
val file = conversationFileURL(conversation.id)
142+
if (file.exists()) {
143+
file.delete()
144+
}
134145
}
135146
}
136147

@@ -176,9 +187,7 @@ class ConversationStore private constructor(context: Context) {
176187
try {
177188
val jsonString = file.readText()
178189
val loaded = json.decodeFromString<Conversation>(jsonString)
179-
val list = _conversations.value.toMutableList()
180-
list.add(loaded)
181-
_conversations.value = list
190+
_conversations.update { list -> list + loaded }
182191
_currentConversation.value = loaded
183192
return loaded
184193
} catch (e: Exception) {
@@ -246,12 +255,14 @@ class ConversationStore private constructor(context: Context) {
246255
* Save a conversation to disk
247256
*/
248257
private fun saveConversation(conversation: Conversation) {
249-
try {
250-
val file = conversationFileURL(conversation.id)
251-
val jsonString = json.encodeToString(conversation)
252-
file.writeText(jsonString)
253-
} catch (e: Exception) {
254-
Timber.e(e, "Failed to save conversation")
258+
ioScope.launch {
259+
try {
260+
val file = conversationFileURL(conversation.id)
261+
val jsonString = json.encodeToString(conversation)
262+
file.writeText(jsonString)
263+
} catch (e: Exception) {
264+
Timber.e(e, "Failed to save conversation")
265+
}
255266
}
256267
}
257268

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.runanywhere.runanywhereai.data
2+
3+
/**
4+
* Example prompts for each LoRA adapter, keyed by adapter filename.
5+
* These are shown in the active LoRA card so users can quickly test the adapter.
6+
*/
7+
object LoraExamplePrompts {
8+
9+
private val promptsByFilename: Map<String, List<String>> = mapOf(
10+
"code-assistant-Q8_0.gguf" to listOf(
11+
"Write a Python function to reverse a linked list",
12+
"Explain the difference between a stack and a queue with code examples",
13+
),
14+
"reasoning-logic-Q8_0.gguf" to listOf(
15+
"If all roses are flowers and some flowers fade quickly, can we conclude some roses fade quickly?",
16+
"A farmer has 17 sheep. All but 9 die. How many are left?",
17+
),
18+
"medical-qa-Q8_0.gguf" to listOf(
19+
"What are the common symptoms of vitamin D deficiency?",
20+
"Explain the difference between Type 1 and Type 2 diabetes",
21+
),
22+
"creative-writing-Q8_0.gguf" to listOf(
23+
"Write a short story about a robot discovering emotions for the first time",
24+
"Describe a sunset over the ocean using vivid sensory language",
25+
),
26+
)
27+
28+
/**
29+
* Get example prompts for a loaded adapter by its file path.
30+
* Extracts the filename from the path and looks up prompts.
31+
*/
32+
fun forAdapterPath(path: String): List<String> {
33+
val filename = path.substringAfterLast("/")
34+
return promptsByFilename[filename] ?: emptyList()
35+
}
36+
}

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/data/ModelList.kt

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,45 @@ object ModelList {
3232
AppModel(id = "qwen2.5-0.5b-instruct-q6_k", name = "Qwen 2.5 0.5B Instruct Q6_K",
3333
url = "https://huggingface.co/Triangle104/Qwen2.5-0.5B-Instruct-Q6_K-GGUF/resolve/main/qwen2.5-0.5b-instruct-q6_k.gguf",
3434
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
35+
memoryRequirement = 600_000_000, supportsLoraAdapters = true),
36+
AppModel(id = "qwen2.5-1.5b-instruct-q4_k_m", name = "Qwen 2.5 1.5B Instruct Q4_K_M",
37+
url = "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/qwen2.5-1.5b-instruct-q4_k_m.gguf",
38+
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
39+
memoryRequirement = 2_500_000_000),
40+
// Qwen3 models
41+
AppModel(id = "qwen3-0.6b-q4_k_m", name = "Qwen3 0.6B Q4_K_M",
42+
url = "https://huggingface.co/unsloth/Qwen3-0.6B-GGUF/resolve/main/Qwen3-0.6B-Q4_K_M.gguf",
43+
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
44+
memoryRequirement = 500_000_000),
45+
AppModel(id = "qwen3-1.7b-q4_k_m", name = "Qwen3 1.7B Q4_K_M",
46+
url = "https://huggingface.co/unsloth/Qwen3-1.7B-GGUF/resolve/main/Qwen3-1.7B-Q4_K_M.gguf",
47+
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
48+
memoryRequirement = 1_200_000_000),
49+
AppModel(id = "qwen3-4b-q4_k_m", name = "Qwen3 4B Q4_K_M",
50+
url = "https://huggingface.co/unsloth/Qwen3-4B-GGUF/resolve/main/Qwen3-4B-Q4_K_M.gguf",
51+
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
52+
memoryRequirement = 2_800_000_000),
53+
// Qwen3.5 models
54+
AppModel(id = "qwen3.5-0.8b-q4_k_m", name = "Qwen3.5 0.8B Q4_K_M",
55+
url = "https://huggingface.co/unsloth/Qwen3.5-0.8B-GGUF/resolve/main/Qwen3.5-0.8B-Q4_K_M.gguf",
56+
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
3557
memoryRequirement = 600_000_000),
58+
AppModel(id = "qwen3.5-2b-q4_k_m", name = "Qwen3.5 2B Q4_K_M",
59+
url = "https://huggingface.co/unsloth/Qwen3.5-2B-GGUF/resolve/main/Qwen3.5-2B-Q4_K_M.gguf",
60+
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
61+
memoryRequirement = 1_500_000_000),
62+
AppModel(id = "qwen3.5-4b-q4_k_m", name = "Qwen3.5 4B Q4_K_M",
63+
url = "https://huggingface.co/unsloth/Qwen3.5-4B-GGUF/resolve/main/Qwen3.5-4B-Q4_K_M.gguf",
64+
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
65+
memoryRequirement = 2_800_000_000),
3666
AppModel(id = "lfm2-350m-q4_k_m", name = "LiquidAI LFM2 350M Q4_K_M",
3767
url = "https://huggingface.co/LiquidAI/LFM2-350M-GGUF/resolve/main/LFM2-350M-Q4_K_M.gguf",
3868
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
39-
memoryRequirement = 250_000_000, supportsLoraAdapters = true),
69+
memoryRequirement = 250_000_000),
4070
AppModel(id = "lfm2-350m-q8_0", name = "LiquidAI LFM2 350M Q8_0",
4171
url = "https://huggingface.co/LiquidAI/LFM2-350M-GGUF/resolve/main/LFM2-350M-Q8_0.gguf",
4272
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
43-
memoryRequirement = 400_000_000, supportsLoraAdapters = true),
73+
memoryRequirement = 400_000_000),
4474
AppModel(id = "lfm2-1.2b-tool-q4_k_m", name = "LiquidAI LFM2 1.2B Tool Q4_K_M",
4575
url = "https://huggingface.co/LiquidAI/LFM2-1.2B-Tool-GGUF/resolve/main/LFM2-1.2B-Tool-Q4_K_M.gguf",
4676
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
@@ -81,46 +111,46 @@ object ModelList {
81111
)),
82112
)
83113

84-
// LoRA Adapters (from Void2377/Qwen on HuggingFace — real standalone LoRA GGUF files)
114+
// LoRA Adapters
85115
private val loraAdapters = listOf(
86116
LoraAdapterCatalogEntry(
87-
id = "chat-assistant-lora",
88-
name = "Chat Assistant",
89-
description = "Enhances conversational chat ability",
90-
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/chat_assistant-lora-Q8_0.gguf",
91-
filename = "chat_assistant-lora-Q8_0.gguf",
92-
compatibleModelIds = listOf("lfm2-350m-q4_k_m", "lfm2-350m-q8_0"),
93-
fileSize = 690_176,
117+
id = "code-assistant-lora",
118+
name = "Code Assistant",
119+
description = "Enhances code generation and programming assistance",
120+
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/code-assistant-Q8_0.gguf",
121+
filename = "code-assistant-Q8_0.gguf",
122+
compatibleModelIds = listOf("qwen2.5-0.5b-instruct-q6_k"),
123+
fileSize = 765_952,
94124
defaultScale = 1.0f,
95125
),
96126
LoraAdapterCatalogEntry(
97-
id = "summarizer-lora",
98-
name = "Summarizer",
99-
description = "Specialized for text summarization tasks",
100-
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/summarizer-lora-Q8_0.gguf",
101-
filename = "summarizer-lora-Q8_0.gguf",
102-
compatibleModelIds = listOf("lfm2-350m-q4_k_m", "lfm2-350m-q8_0"),
103-
fileSize = 690_176,
127+
id = "reasoning-logic-lora",
128+
name = "Reasoning Logic",
129+
description = "Improves logical reasoning and step-by-step problem solving",
130+
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/reasoning-logic-Q8_0.gguf",
131+
filename = "reasoning-logic-Q8_0.gguf",
132+
compatibleModelIds = listOf("qwen2.5-0.5b-instruct-q6_k"),
133+
fileSize = 765_952,
104134
defaultScale = 1.0f,
105135
),
106136
LoraAdapterCatalogEntry(
107-
id = "translator-lora",
108-
name = "Translator",
109-
description = "Improves translation between languages",
110-
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/translator-lora-Q8_0.gguf",
111-
filename = "translator-lora-Q8_0.gguf",
112-
compatibleModelIds = listOf("lfm2-350m-q4_k_m", "lfm2-350m-q8_0"),
113-
fileSize = 690_176,
137+
id = "medical-qa-lora",
138+
name = "Medical QA",
139+
description = "Enhances medical question answering and health-related responses",
140+
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/medical-qa-Q8_0.gguf",
141+
filename = "medical-qa-Q8_0.gguf",
142+
compatibleModelIds = listOf("qwen2.5-0.5b-instruct-q6_k"),
143+
fileSize = 765_952,
114144
defaultScale = 1.0f,
115145
),
116146
LoraAdapterCatalogEntry(
117-
id = "sentiment-lora",
118-
name = "Sentiment Analysis",
119-
description = "Fine-tuned for sentiment analysis tasks",
120-
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/sentiment-lora-Q8_0.gguf",
121-
filename = "sentiment-lora-Q8_0.gguf",
122-
compatibleModelIds = listOf("lfm2-350m-q4_k_m", "lfm2-350m-q8_0"),
123-
fileSize = 690_176,
147+
id = "creative-writing-lora",
148+
name = "Creative Writing",
149+
description = "Improves creative writing, storytelling, and literary style",
150+
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/creative-writing-Q8_0.gguf",
151+
filename = "creative-writing-Q8_0.gguf",
152+
compatibleModelIds = listOf("qwen2.5-0.5b-instruct-q6_k"),
153+
fileSize = 765_952,
124154
defaultScale = 1.0f,
125155
),
126156
)

0 commit comments

Comments
 (0)